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