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