tmp
[apps/madmutt.git] / imap / browse.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-9 Brandon Long <blong@fiction.net>
4  * Copyright (C) 1999-2002 Brendan Cully <brendan@kublai.com>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 /* Mutt browser support routines */
12
13 #include <lib-lib/lib-lib.h>
14
15 #include "mutt.h"
16 #include "imap_private.h"
17
18 /* -- forward declarations -- */
19 static int browse_add_list_result (IMAP_DATA * idata, const char *cmd,
20                                    struct browser_state *state,
21                                    short isparent);
22 static void imap_add_folder (char delim, char *folder, int noselect,
23                              int noinferiors, struct browser_state *state,
24                              short isparent);
25 static int compare_names (struct folder_file *a, struct folder_file *b);
26 static int browse_get_namespace (IMAP_DATA * idata, char *nsbuf, int nsblen,
27                                  IMAP_NAMESPACE_INFO * nsi, int nsilen,
28                                  int *nns);
29 static int browse_verify_namespace (IMAP_DATA * idata,
30                                     IMAP_NAMESPACE_INFO * nsi, int nns);
31
32 /* imap_browse: IMAP hook into the folder browser, fills out browser_state,
33  *   given a current folder to browse */
34 int imap_browse (char *path, struct browser_state *state)
35 {
36   IMAP_DATA *idata;
37   char buf[LONG_STRING];
38   char buf2[LONG_STRING];
39   char nsbuf[LONG_STRING];
40   char mbox[LONG_STRING];
41   char list_cmd[5];
42   IMAP_NAMESPACE_INFO nsi[16];
43   int home_namespace = 0;
44   int n;
45   int i;
46   int nsup;
47   char ctmp;
48   int nns = 0;
49   char *cur_folder;
50   short showparents = 0;
51   int noselect;
52   int noinferiors;
53   int save_lsub;
54   IMAP_MBOX mx;
55
56   if (imap_parse_path (path, &mx)) {
57     mutt_error (_("%s is an invalid IMAP path"), path);
58     return -1;
59   }
60
61   save_lsub = option (OPTIMAPCHECKSUBSCRIBED);
62   unset_option (OPTIMAPCHECKSUBSCRIBED);
63   m_strcpy(list_cmd, sizeof(list_cmd),
64            option(OPTIMAPLSUB) ? "LSUB" : "LIST");
65
66   if (!(idata = imap_conn_find (&(mx.account), 0)))
67     goto fail;
68
69   if (!mx.mbox) {
70     home_namespace = 1;
71     mbox[0] = '\0';             /* Do not replace "" with "INBOX" here */
72     if (mutt_bit_isset (idata->capabilities, NAMESPACE)) {
73       mutt_message _("Getting namespaces...");
74
75       if (browse_get_namespace (idata, nsbuf, sizeof (nsbuf),
76                                 nsi, sizeof (nsi), &nns) != 0)
77         goto fail;
78       if (browse_verify_namespace (idata, nsi, nns) != 0)
79         goto fail;
80     }
81   }
82
83   mutt_message _("Getting folder list...");
84
85   /* skip check for parents when at the root */
86   if (mx.mbox && mx.mbox[0] != '\0') {
87     imap_fix_path (idata, mx.mbox, mbox, sizeof (mbox));
88     imap_munge_mbox_name (buf, sizeof (buf), mbox);
89     imap_unquote_string (buf);  /* As kludgy as it gets */
90     m_strcpy(mbox, sizeof(mbox), buf);
91     n = m_strlen(mbox);
92
93     /* if our target exists and has inferiors, enter it if we
94      * aren't already going to */
95     if (mbox[n - 1] != idata->delim) {
96       snprintf (buf, sizeof (buf), "%s \"\" \"%s\"", list_cmd, mbox);
97       imap_cmd_start (idata, buf);
98       do {
99         if (imap_parse_list_response (idata, &cur_folder, &noselect,
100                                       &noinferiors, &idata->delim) != 0)
101           goto fail;
102
103         if (cur_folder) {
104           imap_unmunge_mbox_name (cur_folder);
105
106           if (!noinferiors && cur_folder[0] &&
107               (n = m_strlen(mbox)) < LONG_STRING - 1) {
108             mbox[n++] = idata->delim;
109             mbox[n] = '\0';
110           }
111         }
112       } while (m_strncmp(idata->cmd.buf.data, idata->cmd.seq, SEQLEN));
113     }
114
115     /* if we're descending a folder, mark it as current in browser_state */
116     if (mbox[n - 1] == idata->delim) {
117       /* don't show parents in the home namespace */
118       if (!home_namespace)
119         showparents = 1;
120       imap_qualify_path (buf, sizeof (buf), &mx, mbox);
121       state->folder = m_strdup(buf);
122       n--;
123     }
124
125     /* Find superiors to list
126      * Note: UW-IMAP servers return folder + delimiter when asked to list
127      *  folder + delimiter. Cyrus servers don't. So we ask for folder,
128      *  and tack on delimiter ourselves.
129      * Further note: UW-IMAP servers return nothing when asked for 
130      *  NAMESPACES without delimiters at the end. Argh! */
131     for (n--; n >= 0 && mbox[n] != idata->delim; n--);
132     if (n > 0) {                /* "aaaa/bbbb/" -> "aaaa" */
133       /* forget the check, it is too delicate (see above). Have we ever
134        * had the parent not exist? */
135       ctmp = mbox[n];
136       mbox[n] = '\0';
137
138       if (showparents) {
139         imap_add_folder (idata->delim, mbox, 1, 0, state, 1);
140       }
141
142       /* if our target isn't a folder, we are in our superior */
143       if (!state->folder) {
144         /* store folder with delimiter */
145         mbox[n++] = ctmp;
146         ctmp = mbox[n];
147         mbox[n] = '\0';
148         imap_qualify_path (buf, sizeof (buf), &mx, mbox);
149         state->folder = m_strdup(buf);
150       }
151       mbox[n] = ctmp;
152     }
153     /* "/bbbb/" -> add  "/", "aaaa/" -> add "" */
154     else {
155       char relpath[2];
156
157       /* folder may be "/" */
158       snprintf (relpath, sizeof (relpath), "%c", n < 0 ? '\0' : idata->delim);
159       if (showparents)
160         imap_add_folder (idata->delim, relpath, 1, 0, state, 1);
161       if (!state->folder) {
162         imap_qualify_path (buf, sizeof (buf), &mx, relpath);
163         state->folder = m_strdup(buf);
164       }
165     }
166   }
167
168   /* no namespace, no folder: set folder to host only */
169   if (!state->folder) {
170     imap_qualify_path (buf, sizeof (buf), &mx, NULL);
171     state->folder = m_strdup(buf);
172   }
173
174   if (home_namespace && mbox[0] != '\0') {
175     /* Listing the home namespace, so INBOX should be included. Home 
176      * namespace is not "", so we have to list it explicitly. We ask the 
177      * server to see if it has descendants. */
178     if (browse_add_list_result (idata, "LIST \"\" \"INBOX\"", state, 0))
179       goto fail;
180   }
181
182   nsup = state->entrylen;
183
184   snprintf (buf, sizeof (buf), "%s%%", mbox);
185   imap_quote_string (buf2, sizeof (buf2), buf);
186   snprintf (buf, sizeof (buf), "%s \"\" %s", list_cmd, buf2);
187   if (browse_add_list_result (idata, buf, state, 0))
188     goto fail;
189
190   if (!state->entrylen) {
191     mutt_error _("No such folder");
192
193     goto fail;
194   }
195
196   mutt_clear_error ();
197
198   qsort (&(state->entry[nsup]), state->entrylen - nsup,
199          sizeof (state->entry[0]),
200          (int (*)(const void *, const void *)) compare_names);
201   if (home_namespace) {         /* List additional namespaces */
202     for (i = 0; i < nns; i++)
203       if (nsi[i].listable && !nsi[i].home_namespace) {
204         imap_add_folder (nsi[i].delim, nsi[i].prefix, nsi[i].noselect,
205                          nsi[i].noinferiors, state, 0);
206       }
207   }
208
209   if (save_lsub)
210     set_option (OPTIMAPCHECKSUBSCRIBED);
211
212   p_delete(&mx.mbox);
213   return 0;
214
215 fail:
216   if (save_lsub)
217     set_option (OPTIMAPCHECKSUBSCRIBED);
218   p_delete(&mx.mbox);
219   return -1;
220 }
221
222 /* imap_mailbox_create: Prompt for a new mailbox name, and try to create it */
223 int imap_mailbox_create (const char *folder)
224 {
225   IMAP_DATA *idata;
226   IMAP_MBOX mx;
227   char buf[LONG_STRING];
228   short n;
229
230   if (imap_parse_path (folder, &mx) < 0) {
231     return -1;
232   }
233
234   if (!(idata = imap_conn_find (&mx.account, M_IMAP_CONN_NONEW))) {
235     goto fail;
236   }
237
238   m_strcpy(buf, sizeof(buf), NONULL(mx.mbox));
239
240   /* append a delimiter if necessary */
241   n = m_strlen(buf);
242   if (n && (n < ssizeof (buf) - 1) && (buf[n - 1] != idata->delim)) {
243     buf[n++] = idata->delim;
244     buf[n] = '\0';
245   }
246
247   if (mutt_get_field (_("Create mailbox: "), buf, sizeof (buf), M_FILE) < 0)
248     goto fail;
249
250   if (!m_strlen(buf)) {
251     mutt_error (_("Mailbox must have a name."));
252     mutt_sleep (1);
253     goto fail;
254   }
255
256   if (imap_create_mailbox (idata, buf) < 0)
257     goto fail;
258
259   mutt_message _("Mailbox created.");
260
261   mutt_sleep (0);
262
263   p_delete(&mx.mbox);
264   return 0;
265
266 fail:
267   p_delete(&mx.mbox);
268   return -1;
269 }
270
271 int imap_mailbox_rename (const char *mailbox)
272 {
273   IMAP_DATA *idata;
274   IMAP_MBOX mx;
275   char buf[LONG_STRING];
276   char newname[STRING];
277
278   if (imap_parse_path (mailbox, &mx) < 0) {
279     return -1;
280   }
281
282   if (!(idata = imap_conn_find (&mx.account, M_IMAP_CONN_NONEW))) {
283     goto fail;
284   }
285
286   snprintf (buf, sizeof (buf), _("Rename mailbox %s to: "), mx.mbox);
287
288   if (mutt_get_field (buf, newname, sizeof (newname), M_FILE) < 0)
289     goto fail;
290
291   if (!m_strlen(newname)) {
292     mutt_error (_("Mailbox must have a name."));
293     mutt_sleep (1);
294     goto fail;
295   }
296
297   if (imap_rename_mailbox (idata, &mx, newname) < 0) {
298     mutt_error (_("Rename failed: %s"),
299                 imap_get_qualifier(idata->cmd.buf.data));
300     mutt_sleep (1);
301     goto fail;
302   }
303
304   mutt_message (_("Mailbox renamed."));
305   mutt_sleep (0);
306
307   p_delete(&mx.mbox);
308   return 0;
309
310 fail:
311   p_delete(&mx.mbox);
312   return -1;
313 }
314
315 static int browse_add_list_result (IMAP_DATA * idata, const char *cmd,
316                                    struct browser_state *state,
317                                    short isparent)
318 {
319   char *name;
320   int noselect;
321   int noinferiors;
322   IMAP_MBOX mx;
323
324   if (imap_parse_path (state->folder, &mx)) {
325     return -1;
326   }
327
328   imap_cmd_start (idata, cmd);
329
330   do {
331     if (imap_parse_list_response (idata, &name, &noselect, &noinferiors,
332                                   &idata->delim) != 0) {
333       p_delete(&mx.mbox);
334       return -1;
335     }
336
337     if (name) {
338       /* Let a parent folder never be selectable for navigation */
339       if (isparent)
340         noselect = 1;
341       /* prune current folder from output */
342       if (isparent || m_strncmp(name, mx.mbox, m_strlen(name)))
343         imap_add_folder (idata->delim, name, noselect, noinferiors, state,
344                          isparent);
345     }
346   } while ((m_strncmp(idata->cmd.buf.data, idata->cmd.seq, SEQLEN) != 0));
347
348   p_delete(&mx.mbox);
349   return 0;
350 }
351
352 /* imap_add_folder: add a folder name to the browser list, formatting it as
353  *   necessary. */
354 static void imap_add_folder (char delim, char *folder, int noselect,
355                              int noinferiors, struct browser_state *state,
356                              short isparent)
357 {
358   char tmp[LONG_STRING];
359   char relpath[LONG_STRING];
360   IMAP_MBOX mx;
361
362   if (imap_parse_path (state->folder, &mx))
363     return;
364
365   imap_unmunge_mbox_name (folder);
366
367   if (state->entrylen + 1 == state->entrymax) {
368     p_realloc(&state->entry, state->entrymax += 256);
369     p_clear(state->entry + state->entrylen,
370             state->entrymax - state->entrylen);
371   }
372
373   /* render superiors as unix-standard ".." */
374   if (isparent)
375     m_strcpy(relpath, sizeof(relpath), "../");
376   /* strip current folder from target, to render a relative path */
377   else if (!m_strncmp(mx.mbox, folder, m_strlen(mx.mbox)))
378     m_strcpy(relpath, sizeof(relpath), folder + m_strlen(mx.mbox));
379   else
380     m_strcpy(relpath, sizeof(relpath), folder);
381
382   /* apply filemask filter. This should really be done at menu setup rather
383    * than at scan, since it's so expensive to scan. But that's big changes
384    * to browser.c */
385   if (!((regexec (Mask.rx, relpath, 0, NULL, 0) == 0) ^ Mask.neg)) {
386     p_delete(&mx.mbox);
387     return;
388   }
389
390   imap_qualify_path (tmp, sizeof (tmp), &mx, folder);
391   (state->entry)[state->entrylen].name = m_strdup(tmp);
392
393   /* mark desc with delim in browser if it can have subfolders */
394   if (!isparent && !noinferiors && m_strlen(relpath) < ssizeof (relpath) - 1) {
395     relpath[m_strlen(relpath) + 1] = '\0';
396     relpath[m_strlen(relpath)] = delim;
397   }
398
399   (state->entry)[state->entrylen].desc = m_strdup(relpath);
400
401   (state->entry)[state->entrylen].imap = 1;
402   /* delimiter at the root is useless. */
403   if (folder[0] == '\0')
404     delim = '\0';
405   (state->entry)[state->entrylen].delim = delim;
406   (state->entry)[state->entrylen].selectable = !noselect;
407   (state->entry)[state->entrylen].inferiors = !noinferiors;
408   (state->entrylen)++;
409
410   p_delete(&mx.mbox);
411 }
412
413 static int compare_names (struct folder_file *a, struct folder_file *b)
414 {
415   return m_strcmp(a->name, b->name);
416 }
417
418 static int browse_get_namespace (IMAP_DATA * idata, char *nsbuf, int nsblen,
419                                  IMAP_NAMESPACE_INFO * nsi, int nsilen,
420                                  int *nns)
421 {
422   char *s;
423   int n;
424   char ns[LONG_STRING];
425   char delim = '/';
426   int type;
427   int nsbused = 0;
428   int rc;
429
430   *nns = 0;
431   nsbuf[nsblen - 1] = '\0';
432
433   imap_cmd_start (idata, "NAMESPACE");
434
435   do {
436     if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
437       break;
438
439     s = imap_next_word (idata->cmd.buf.data);
440     if (ascii_strncasecmp ("NAMESPACE", s, 9) == 0) {
441       /* There are three sections to the response, User, Other, Shared,
442        * and maybe more by extension */
443       for (type = IMAP_NS_PERSONAL; *s; type++) {
444         s = imap_next_word (s);
445         if (*s && ascii_strncasecmp (s, "NIL", 3)) {
446           s++;
447           while (*s && *s != ')') {
448             s++;                /* skip ( */
449             /* copy namespace */
450             n = 0;
451             delim = '\0';
452
453             if (*s == '\"') {
454               s++;
455               while (*s && *s != '\"') {
456                 if (*s == '\\')
457                   s++;
458                 ns[n++] = *s;
459                 s++;
460               }
461               if (*s)
462                 s++;
463             }
464             else
465               while (*s && !ISSPACE (*s)) {
466                 ns[n++] = *s;
467                 s++;
468               }
469             ns[n] = '\0';
470             /* delim? */
471             s = imap_next_word (s);
472             /* delimiter is meaningless if namespace is "". Why does
473              * Cyrus provide one?! */
474             if (n && *s && *s == '\"') {
475               if (s[1] && s[2] == '\"')
476                 delim = s[1];
477               else if (s[1] && s[1] == '\\' && s[2] && s[3] == '\"')
478                 delim = s[2];
479             }
480             /* skip "" namespaces, they are already listed at the root */
481             if ((ns[0] != '\0') && (nsbused < nsblen) && (*nns < nsilen)) {
482               nsi->type = type;
483               /* Cyrus doesn't append the delimiter to the namespace,
484                * but UW-IMAP does. We'll strip it here and add it back
485                * as if it were a normal directory, from the browser */
486               if (n && (ns[n - 1] == delim))
487                 ns[--n] = '\0';
488               strncpy (nsbuf + nsbused, ns, nsblen - nsbused - 1);
489               nsi->prefix = nsbuf + nsbused;
490               nsbused += n + 1;
491               nsi->delim = delim;
492               nsi++;
493               (*nns)++;
494             }
495             while (*s && *s != ')')
496               s++;
497             if (*s)
498               s++;
499           }
500         }
501       }
502     }
503   }
504   while (rc == IMAP_CMD_CONTINUE);
505
506   if (rc != IMAP_CMD_OK)
507     return -1;
508
509   return 0;
510 }
511
512 /* Check which namespaces have contents */
513 static int browse_verify_namespace (IMAP_DATA * idata,
514                                     IMAP_NAMESPACE_INFO * nsi, int nns)
515 {
516   char buf[LONG_STRING];
517   int i = 0;
518   char *name;
519   char delim;
520
521   for (i = 0; i < nns; i++, nsi++) {
522     /* Cyrus gives back nothing if the % isn't added. This may return lots
523      * of data in some cases, I guess, but I currently feel that's better
524      * than invisible namespaces */
525     if (nsi->delim)
526       snprintf (buf, sizeof (buf), "%s \"\" \"%s%c%%\"",
527                 option (OPTIMAPLSUB) ? "LSUB" : "LIST", nsi->prefix,
528                 nsi->delim);
529     else
530       snprintf (buf, sizeof (buf), "%s \"\" \"%s%%\"",
531                 option (OPTIMAPLSUB) ? "LSUB" : "LIST", nsi->prefix);
532
533     imap_cmd_start (idata, buf);
534
535     nsi->listable = 0;
536     nsi->home_namespace = 0;
537     do {
538       if (imap_parse_list_response (idata, &name, &nsi->noselect,
539                                     &nsi->noinferiors, &delim) != 0)
540         return -1;
541       nsi->listable |= (name != NULL);
542     } while ((m_strncmp(idata->cmd.buf.data, idata->cmd.seq, SEQLEN) != 0));
543   }
544
545   return 0;
546 }