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