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