const, style
[apps/madmutt.git] / imap / imap.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-8 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 1996-9 Brandon Long <blong@fiction.net>
5  * Copyright (C) 1999-2005 Brendan Cully <brendan@kublai.com>
6  *
7  * This file is part of mutt-ng, see http://www.muttng.org/.
8  * It's licensed under the GNU General Public License,
9  * please see the file GPL in the top level source directory.
10  */
11
12 /* Support for IMAP4rev1, with the occasional nod to IMAP 4. */
13
14 #include <lib-lib/lib-lib.h>
15 #include <lib-mx/mx.h>
16
17 #include "mutt.h"
18 #include "globals.h"
19 #include "pattern.h"
20 #include "sort.h"
21 #include "browser.h"
22 #include "message.h"
23 #include "imap_private.h"
24 #include "buffy.h"
25
26 /* imap forward declarations */
27 static int imap_get_delim (IMAP_DATA * idata);
28 static char *imap_get_flags (string_list_t ** hflags, char *s);
29 static int imap_check_acl (IMAP_DATA * idata);
30 static int imap_check_capabilities (IMAP_DATA * idata);
31 static void imap_set_flag (IMAP_DATA * idata, int aclbit, int flag,
32                            const char *str, char *flags, size_t flsize);
33
34 /* imap_access: Check permissions on an IMAP mailbox.
35  * TODO: ACL checks. Right now we assume if it exists we can
36  * mess with it. */
37 static int imap_access (const char *path, int flags __attribute__ ((unused)))
38 {
39   IMAP_DATA *idata;
40   IMAP_MBOX mx;
41   char buf[LONG_STRING];
42   char mailbox[LONG_STRING];
43   char mbox[LONG_STRING];
44
45   if (imap_parse_path (path, &mx))
46     return -1;
47
48   if (!(idata = imap_conn_find (&mx.account,
49                                 option (OPTIMAPPASSIVE) ? M_IMAP_CONN_NONEW :
50                                 0))) {
51     p_delete(&mx.mbox);
52     return -1;
53   }
54
55   imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
56
57   /* we may already be in the folder we're checking */
58   if (!m_strcmp(idata->mailbox, mx.mbox)) {
59     p_delete(&mx.mbox);
60     return 0;
61   }
62
63   p_delete(&mx.mbox);
64   imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
65
66   if (mutt_bit_isset (idata->capabilities, IMAP4REV1))
67     snprintf (buf, sizeof (buf), "STATUS %s (UIDVALIDITY)", mbox);
68   else if (mutt_bit_isset (idata->capabilities, STATUS))
69     snprintf (buf, sizeof (buf), "STATUS %s (UID-VALIDITY)", mbox);
70   else {
71     return -1;
72   }
73
74   if (imap_exec (idata, buf, IMAP_CMD_FAIL_OK) < 0) {
75     return -1;
76   }
77
78   return 0;
79 }
80
81 int imap_create_mailbox (IMAP_DATA * idata, char *mailbox)
82 {
83   char buf[LONG_STRING], mbox[LONG_STRING];
84
85   imap_munge_mbox_name (mbox, sizeof (mbox), mailbox);
86   snprintf (buf, sizeof (buf), "CREATE %s", mbox);
87
88   if (imap_exec (idata, buf, 0) != 0)
89     return -1;
90
91   return 0;
92 }
93
94 int imap_rename_mailbox (IMAP_DATA * idata, IMAP_MBOX * mx,
95                          const char *newname)
96 {
97   char oldmbox[LONG_STRING];
98   char newmbox[LONG_STRING];
99   char buf[LONG_STRING];
100
101   imap_munge_mbox_name (oldmbox, sizeof (oldmbox), mx->mbox);
102   imap_munge_mbox_name (newmbox, sizeof (newmbox), newname);
103
104   snprintf (buf, sizeof (buf), "RENAME %s %s", oldmbox, newmbox);
105
106   if (imap_exec (idata, buf, 0) != 0)
107     return -1;
108
109   return 0;
110 }
111
112 int imap_delete_mailbox (CONTEXT * ctx, IMAP_MBOX mx)
113 {
114   char buf[LONG_STRING], mbox[LONG_STRING];
115   IMAP_DATA *idata;
116
117   if (!ctx || !ctx->data) {
118     if (!(idata = imap_conn_find (&mx.account,
119                                   option (OPTIMAPPASSIVE) ? M_IMAP_CONN_NONEW
120                                   : 0))) {
121       p_delete(&mx.mbox);
122       return -1;
123     }
124   }
125   else {
126     idata = ctx->data;
127   }
128
129   imap_munge_mbox_name (mbox, sizeof (mbox), mx.mbox);
130   snprintf (buf, sizeof (buf), "DELETE %s", mbox);
131
132   if (imap_exec ((IMAP_DATA *) idata, buf, 0) != 0)
133     return -1;
134
135   return 0;
136 }
137
138 /* imap_logout_all: close all open connections. Quick and dirty until we can
139  *   make sure we've got all the context we need. */
140 void imap_logout_all (void)
141 {
142   CONNECTION *conn;
143   CONNECTION *tmp;
144
145   conn = mutt_socket_head ();
146
147   while (conn) {
148     tmp = conn->next;
149
150     if (conn->account.type == M_ACCT_TYPE_IMAP && conn->fd >= 0) {
151       mutt_message (_("Closing connection to %s..."), conn->account.host);
152       imap_logout ((IMAP_DATA *) conn->data);
153       mutt_clear_error ();
154       mutt_socket_close (conn);
155       mutt_socket_free (conn);
156     }
157
158     conn = tmp;
159   }
160 }
161
162 /* imap_read_literal: read bytes bytes from server into file. Not explicitly
163  *   buffered, relies on FILE buffering. NOTE: strips \r from \r\n.
164  *   Apparently even literals use \r\n-terminated strings ?! */
165 int imap_read_literal (FILE * fp, IMAP_DATA * idata, long bytes, progress_t* bar)
166 {
167   long pos;
168   char c;
169
170   int r = 0;
171
172   for (pos = 0; pos < bytes; pos++) {
173     if (mutt_socket_readchar (idata->conn, &c) != 1) {
174       idata->status = IMAP_FATAL;
175
176       return -1;
177     }
178
179     if (r == 1 && c != '\n')
180       fputc ('\r', fp);
181
182     if (c == '\r') {
183       r = 1;
184       continue;
185     }
186     else
187       r = 0;
188
189     fputc (c, fp);
190     if (bar && !(pos % 1024))
191       mutt_progress_bar (bar, pos);
192   }
193
194   return 0;
195 }
196
197 /* imap_expunge_mailbox: Purge IMAP portion of expunged messages from the
198  *   context. Must not be done while something has a handle on any headers
199  *   (eg inside pager or editor). That is, check IMAP_REOPEN_ALLOW. */
200 void imap_expunge_mailbox (IMAP_DATA * idata)
201 {
202   HEADER *h;
203   int i, cacheno;
204
205   for (i = 0; i < idata->ctx->msgcount; i++) {
206     h = idata->ctx->hdrs[i];
207
208     if (h->index == -1) {
209       h->active = 0;
210
211       /* free cached body from disk, if neccessary */
212       cacheno = HEADER_DATA (h)->uid % IMAP_CACHE_LEN;
213       if (idata->cache[cacheno].uid == HEADER_DATA (h)->uid &&
214           idata->cache[cacheno].path) {
215         unlink (idata->cache[cacheno].path);
216         p_delete(&idata->cache[cacheno].path);
217       }
218
219       imap_free_header_data (&h->data);
220     }
221   }
222
223   /* We may be called on to expunge at any time. We can't rely on the caller
224    * to always know to rethread */
225   mx_update_tables (idata->ctx, 0);
226   mutt_sort_headers (idata->ctx, 1);
227 }
228
229 static int imap_get_delim (IMAP_DATA * idata)
230 {
231   char *s;
232   int rc;
233
234   /* assume that the delim is /.  If this fails, we're in bigger trouble
235    * than getting the delim wrong */
236   idata->delim = '/';
237
238   imap_cmd_start (idata, "LIST \"\" \"\"");
239
240   do {
241     if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
242       break;
243
244     s = imap_next_word (idata->cmd.buf.data);
245     if (ascii_strncasecmp ("LIST", s, 4) == 0) {
246       s = imap_next_word (s);
247       s = imap_next_word (s);
248       if (s && s[0] == '\"' && s[1] && s[2] == '\"')
249         idata->delim = s[1];
250       else if (s && s[0] == '\"' && s[1] && s[1] == '\\' && s[2]
251                && s[3] == '\"')
252         idata->delim = s[2];
253     }
254   }
255   while (rc == IMAP_CMD_CONTINUE);
256
257   return -1;
258 }
259
260 /* get rights for folder, let imap_handle_untagged do the rest */
261 static int imap_check_acl (IMAP_DATA * idata)
262 {
263   char buf[LONG_STRING];
264   char mbox[LONG_STRING];
265
266   imap_munge_mbox_name (mbox, sizeof (mbox), idata->mailbox);
267   snprintf (buf, sizeof (buf), "MYRIGHTS %s", mbox);
268   if (imap_exec (idata, buf, 0) != 0) {
269     imap_error ("imap_check_acl", buf);
270     return -1;
271   }
272   return 0;
273 }
274
275 /* imap_check_capabilities: make sure we can log in to this server. */
276 static int imap_check_capabilities (IMAP_DATA * idata)
277 {
278   if (imap_exec (idata, "CAPABILITY", 0) != 0) {
279     imap_error ("imap_check_capabilities", idata->cmd.buf.data);
280     return -1;
281   }
282
283   if (!(mutt_bit_isset (idata->capabilities, IMAP4)
284         || mutt_bit_isset (idata->capabilities, IMAP4REV1))) {
285     mutt_error _("This IMAP server is ancient. Mutt does not work with it.");
286
287     mutt_sleep (2);             /* pause a moment to let the user see the error */
288
289     return -1;
290   }
291
292   return 0;
293 }
294
295 /* imap_conn_find: Find an open IMAP connection matching account, or open
296  *   a new one if none can be found. */
297 IMAP_DATA *imap_conn_find (const ACCOUNT * account, int flags)
298 {
299   CONNECTION *conn;
300   IMAP_DATA *idata;
301   ACCOUNT *creds;
302
303   if (!(conn = mutt_conn_find (NULL, account)))
304     return NULL;
305
306   /* if opening a new UNSELECTED connection, preserve existing creds */
307   creds = &(conn->account);
308
309   /* make sure this connection is not in SELECTED state, if neccessary */
310   if (flags & M_IMAP_CONN_NOSELECT)
311     while (conn->data && ((IMAP_DATA *) conn->data)->state == IMAP_SELECTED) {
312       if (!(conn = mutt_conn_find (conn, account)))
313         return NULL;
314       memcpy (&(conn->account), creds, sizeof (ACCOUNT));
315     }
316
317   idata = (IMAP_DATA *) conn->data;
318
319   /* don't open a new connection if one isn't wanted */
320   if (flags & M_IMAP_CONN_NONEW) {
321     if (!idata) {
322       mutt_socket_free (conn);
323       return NULL;
324     }
325     if (idata->state < IMAP_AUTHENTICATED)
326       return NULL;
327   }
328
329   if (!idata) {
330     /* The current connection is a new connection */
331     idata = imap_new_idata();
332     conn->data  = idata;
333     idata->conn = conn;
334   }
335
336   if (idata->state == IMAP_DISCONNECTED)
337     imap_open_connection (idata);
338   if (idata->state == IMAP_CONNECTED) {
339     if (!imap_authenticate(idata)) {
340       idata->state = IMAP_AUTHENTICATED;
341     } else {
342       mutt_socket_close(idata->conn);
343       idata->state = IMAP_DISCONNECTED;
344       idata->conn->account.has_pass = 0;
345     }
346
347     p_delete(&idata->capstr);
348   }
349   if (idata->isnew && idata->state == IMAP_AUTHENTICATED) {
350     imap_get_delim (idata);
351     if (option (OPTIMAPCHECKSUBSCRIBED)) {
352       mutt_message _("Checking mailbox subscriptions");
353       imap_exec (idata, "LSUB \"\" \"*\"", 0);
354     }
355     idata->isnew = 0;
356   }
357
358   return idata;
359 }
360
361 int imap_open_connection (IMAP_DATA * idata)
362 {
363   char buf[LONG_STRING];
364
365   if (mutt_socket_open (idata->conn) < 0)
366     return -1;
367
368   idata->state = IMAP_CONNECTED;
369
370   if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE) {
371     mutt_socket_close (idata->conn);
372     idata->state = IMAP_DISCONNECTED;
373     return -1;
374   }
375
376   if (ascii_strncasecmp ("* OK", idata->cmd.buf.data, 4) == 0) {
377     /* TODO: Parse new tagged CAPABILITY data (* OK [CAPABILITY...]) */
378     if (imap_check_capabilities (idata))
379       goto bail;
380     /* Attempt STARTTLS if available and desired. */
381     if (!idata->conn->ssf && (mod_ssl.force_tls ||
382                               mutt_bit_isset (idata->capabilities, STARTTLS))) {
383       int rc;
384
385       if (mod_ssl.force_tls)
386         rc = M_YES;
387       else if (mod_ssl.starttls) {
388         if ((rc = imap_exec (idata, "STARTTLS", IMAP_CMD_FAIL_OK)) == -1)
389           goto bail;
390         if (rc != -2) {
391           if (mutt_ssl_starttls (idata->conn))
392           {
393             mutt_error (_("Could not negotiate TLS connection"));
394             mutt_sleep (1);
395             goto err_close_conn;
396           }
397           else {
398             /* RFC 2595 demands we recheck CAPABILITY after TLS completes. */
399             if (imap_exec (idata, "CAPABILITY", 0))
400               goto bail;
401           }
402         }
403       }
404     }
405
406     if (mod_ssl.force_tls && ! idata->conn->ssf) {
407       mutt_error _("Encrypted connection unavailable");
408       mutt_sleep (1);
409       goto err_close_conn;
410     }
411   }
412   else if (ascii_strncasecmp ("* PREAUTH", idata->cmd.buf.data, 9) == 0) {
413     idata->state = IMAP_AUTHENTICATED;
414     if (imap_check_capabilities (idata) != 0)
415       goto bail;
416     p_delete(&idata->capstr);
417   }
418   else {
419     imap_error ("imap_open_connection()", buf);
420     goto bail;
421   }
422
423   return 0;
424
425 err_close_conn:
426   mutt_socket_close (idata->conn);
427   idata->state = IMAP_DISCONNECTED;
428 bail:
429   p_delete(&idata->capstr);
430   return -1;
431 }
432
433 /* imap_get_flags: Make a simple list out of a FLAGS response.
434  *   return stream following FLAGS response */
435 static char *imap_get_flags (string_list_t ** hflags, char *s)
436 {
437   string_list_t *flags;
438   char *flag_word;
439   char ctmp;
440
441   /* sanity-check string */
442   if (ascii_strncasecmp ("FLAGS", s, 5) != 0) {
443     return NULL;
444   }
445   s = vskipspaces(s + 5);
446   if (*s != '(') {
447     return NULL;
448   }
449
450   /* create list, update caller's flags handle */
451   flags = string_item_new();
452   *hflags = flags;
453
454   while (*s && *s != ')') {
455     s = vskipspaces(s + 1);
456     flag_word = s;
457     while (*s && (*s != ')') && !ISSPACE (*s))
458       s++;
459     ctmp = *s;
460     *s = '\0';
461     if (*flag_word)
462       mutt_add_list (flags, flag_word);
463     *s = ctmp;
464   }
465
466   /* note bad flags response */
467   if (*s != ')') {
468     string_list_wipe(hflags);
469
470     return NULL;
471   }
472
473   s++;
474
475   return s;
476 }
477
478 static int imap_open_mailbox (CONTEXT * ctx)
479 {
480   CONNECTION *conn;
481   IMAP_DATA *idata;
482   char buf[LONG_STRING];
483   char bufout[LONG_STRING];
484   int count = 0;
485   IMAP_MBOX mx;
486   int rc;
487
488   if (imap_parse_path (ctx->path, &mx)) {
489     mutt_error (_("%s is an invalid IMAP path"), ctx->path);
490     return -1;
491   }
492
493   /* we require a connection which isn't currently in IMAP_SELECTED state */
494   if (!(idata = imap_conn_find (&(mx.account), M_IMAP_CONN_NOSELECT)))
495     goto fail_noidata;
496   if (idata->state < IMAP_AUTHENTICATED)
497     goto fail;
498
499   conn = idata->conn;
500
501   /* once again the context is new */
502   ctx->data = idata;
503
504   /* Clean up path and replace the one in the ctx */
505   imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
506   p_delete(&(idata->mailbox));
507   idata->mailbox = m_strdup(buf);
508   imap_qualify_path (buf, sizeof (buf), &mx, idata->mailbox);
509
510   p_delete(&(ctx->path));
511   ctx->path = m_strdup(buf);
512
513   idata->ctx = ctx;
514
515   /* clear mailbox status */
516   idata->status = 0;
517   memset (idata->rights, 0, (RIGHTSMAX + 7) / 8);
518   idata->newMailCount = 0;
519
520   mutt_message (_("Selecting %s..."), idata->mailbox);
521   imap_munge_mbox_name (buf, sizeof (buf), idata->mailbox);
522   snprintf (bufout, sizeof (bufout), "%s %s",
523             ctx->readonly ? "EXAMINE" : "SELECT", buf);
524
525   idata->state = IMAP_SELECTED;
526
527   imap_cmd_start (idata, bufout);
528
529   do {
530     char *pc;
531
532     if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
533       break;
534
535     pc = idata->cmd.buf.data + 2;
536
537     /* Obtain list of available flags here, may be overridden by a
538      * PERMANENTFLAGS tag in the OK response */
539     if (ascii_strncasecmp ("FLAGS", pc, 5) == 0) {
540       /* don't override PERMANENTFLAGS */
541       if (!idata->flags) {
542         if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
543           goto fail;
544       }
545     }
546     /* PERMANENTFLAGS are massaged to look like FLAGS, then override FLAGS */
547     else if (ascii_strncasecmp ("OK [PERMANENTFLAGS", pc, 18) == 0) {
548       /* safe to call on NULL */
549       string_list_wipe(&(idata->flags));
550       /* skip "OK [PERMANENT" so syntax is the same as FLAGS */
551       pc += 13;
552       if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
553         goto fail;
554     }
555 #ifdef USE_HCACHE
556     /* save UIDVALIDITY for the header cache */
557     else if (ascii_strncasecmp ("OK [UIDVALIDITY", pc, 14) == 0) {
558       pc += 3;
559       pc = imap_next_word (pc);
560
561       sscanf (pc, "%lu", &(idata->uid_validity));
562     }
563 #endif
564     else {
565       pc = imap_next_word (pc);
566       if (!ascii_strncasecmp ("EXISTS", pc, 6)) {
567         count = idata->newMailCount;
568         idata->newMailCount = 0;
569       }
570     }
571   }
572   while (rc == IMAP_CMD_CONTINUE);
573
574   if (rc == IMAP_CMD_NO) {
575     char *s;
576
577     s = imap_next_word (idata->cmd.buf.data);        /* skip seq */
578     s = imap_next_word (s);     /* Skip response */
579     mutt_error ("%s", s);
580     mutt_sleep (2);
581     goto fail;
582   }
583
584   if (rc != IMAP_CMD_OK)
585     goto fail;
586
587   /* check for READ-ONLY notification */
588   if (!ascii_strncasecmp
589       (imap_get_qualifier (idata->cmd.buf.data), "[READ-ONLY]", 11)
590       && !mutt_bit_isset (idata->capabilities, ACL)) {
591     ctx->readonly = 1;
592   }
593
594   if (mutt_bit_isset (idata->capabilities, ACL)) {
595     if (imap_check_acl (idata))
596       goto fail;
597     if (!(mutt_bit_isset (idata->rights, ACL_DELETE) ||
598           mutt_bit_isset (idata->rights, ACL_SEEN) ||
599           mutt_bit_isset (idata->rights, ACL_WRITE) ||
600           mutt_bit_isset (idata->rights, ACL_INSERT)))
601       ctx->readonly = 1;
602   }
603   /* assume we have all rights if ACL is unavailable */
604   else {
605     mutt_bit_set (idata->rights, ACL_LOOKUP);
606     mutt_bit_set (idata->rights, ACL_READ);
607     mutt_bit_set (idata->rights, ACL_SEEN);
608     mutt_bit_set (idata->rights, ACL_WRITE);
609     mutt_bit_set (idata->rights, ACL_INSERT);
610     mutt_bit_set (idata->rights, ACL_POST);
611     mutt_bit_set (idata->rights, ACL_CREATE);
612     mutt_bit_set (idata->rights, ACL_DELETE);
613   }
614
615   ctx->hdrmax = count;
616   ctx->hdrs = p_new(HEADER *, count);
617   ctx->v2r = p_new(int, count);
618   ctx->msgcount = 0;
619   if (count && (imap_read_headers (idata, 0, count - 1) < 0)) {
620     mutt_error _("Error opening mailbox");
621
622     mutt_sleep (1);
623     goto fail;
624   }
625
626   p_delete(&mx.mbox);
627   return 0;
628
629 fail:
630   if (idata->state == IMAP_SELECTED)
631     idata->state = IMAP_AUTHENTICATED;
632 fail_noidata:
633   p_delete(&mx.mbox);
634   return -1;
635 }
636
637 int imap_open_mailbox_append (CONTEXT * ctx)
638 {
639   CONNECTION *conn;
640   IMAP_DATA *idata;
641   char buf[LONG_STRING];
642   char mailbox[LONG_STRING];
643   IMAP_MBOX mx;
644
645   if (imap_parse_path (ctx->path, &mx))
646     return -1;
647
648   /* in APPEND mode, we appear to hijack an existing IMAP connection -
649    * ctx is brand new and mostly empty */
650
651   if (!(idata = imap_conn_find (&(mx.account), 0))) {
652     p_delete(&mx.mbox);
653     return (-1);
654   }
655   conn = idata->conn;
656
657   ctx->magic = M_IMAP;
658   ctx->data = idata;
659
660   imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
661
662   p_delete(&mx.mbox);
663
664   /* really we should also check for W_OK */
665   if (!imap_access (ctx->path, F_OK))
666     return 0;
667
668   snprintf (buf, sizeof (buf), _("Create %s?"), mailbox);
669   if (option (OPTCONFIRMCREATE) && mutt_yesorno (buf, 1) < 1)
670     return -1;
671
672   if (imap_create_mailbox (idata, mailbox) < 0)
673     return -1;
674
675   return (0);
676 }
677
678 /* imap_logout: Gracefully log out of server. */
679 void imap_logout (IMAP_DATA * idata)
680 {
681   /* we set status here to let imap_handle_untagged know we _expect_ to
682    * receive a bye response (so it doesn't freak out and close the conn) */
683   idata->status = IMAP_BYE;
684   imap_cmd_start (idata, "LOGOUT");
685   while (imap_cmd_step (idata) == IMAP_CMD_CONTINUE);
686   imap_free_idata(&idata);
687 }
688
689 /* imap_set_flag: append str to flags if we currently have permission
690  *   according to aclbit */
691 static void imap_set_flag(IMAP_DATA *idata, int aclbit, int flag,
692                           const char *str, char *flags, size_t flsize)
693 {
694     if (mutt_bit_isset(idata->rights, aclbit)) {
695         if (flag)
696             m_strcat(flags, flsize, str);
697     }
698 }
699
700 /* imap_make_msg_set: make an IMAP4rev1 UID message set out of a set of
701  *   headers, given a flag enum to filter on.
702  * Params: idata: IMAP_DATA containing context containing header set
703  *         buf: to write message set into
704  *         buflen: length of buffer
705  *         flag: enum of flag type on which to filter
706  *         changed: include only changed messages in message set
707  * Returns: number of messages in message set (0 if no matches) */
708 int imap_make_msg_set (IMAP_DATA * idata, BUFFER * buf, int flag, int changed)
709 {
710   HEADER **hdrs;                /* sorted local copy */
711   int count = 0;                /* number of messages in message set */
712   int match = 0;                /* whether current message matches flag condition */
713   int setstart = 0;             /* start of current message range */
714   int n;
715   short oldsort;                /* we clobber reverse, must restore it */
716
717   /* assuming 32-bit UIDs */
718   char uid[12];
719   int started = 0;
720
721   /* make copy of header pointers to sort in natural order */
722   hdrs = p_dup(idata->ctx->hdrs, idata->ctx->msgcount);
723
724   if (Sort != SORT_ORDER) {
725     oldsort = Sort;
726     Sort = SORT_ORDER;
727     qsort ((void *) hdrs, idata->ctx->msgcount, sizeof (HEADER *),
728            mutt_get_sort_func (SORT_ORDER));
729     Sort = oldsort;
730   }
731
732   for (n = 0; n < idata->ctx->msgcount; n++) {
733     match = 0;
734     /* don't include pending expunged messages */
735     if (hdrs[n]->active)
736       switch (flag) {
737       case M_DELETE:
738         if (hdrs[n]->deleted)
739           match = 1;
740         break;
741       case M_TAG:
742         if (hdrs[n]->tagged)
743           match = 1;
744         break;
745       }
746
747     if (match && (!changed || hdrs[n]->changed)) {
748       count++;
749       if (setstart == 0) {
750         setstart = HEADER_DATA (hdrs[n])->uid;
751         if (started == 0) {
752           snprintf (uid, sizeof (uid), "%u", HEADER_DATA (hdrs[n])->uid);
753           mutt_buffer_addstr (buf, uid);
754           started = 1;
755         }
756         else {
757           snprintf (uid, sizeof (uid), ",%u", HEADER_DATA (hdrs[n])->uid);
758           mutt_buffer_addstr (buf, uid);
759         }
760       }
761       /* tie up if the last message also matches */
762       else if (n == idata->ctx->msgcount - 1) {
763         snprintf (uid, sizeof (uid), ":%u", HEADER_DATA (hdrs[n])->uid);
764         mutt_buffer_addstr (buf, uid);
765       }
766     }
767     /* this message is not expunged and doesn't match. End current set. */
768     else if (setstart && hdrs[n]->active) {
769       if (HEADER_DATA (hdrs[n - 1])->uid > setstart) {
770         snprintf (uid, sizeof (uid), ":%u", HEADER_DATA (hdrs[n - 1])->uid);
771         mutt_buffer_addstr (buf, uid);
772       }
773       setstart = 0;
774     }
775   }
776
777   p_delete(&hdrs);
778
779   return count;
780 }
781
782 /* Update the IMAP server to reflect the flags a single message.  */
783
784 int imap_sync_message (IMAP_DATA *idata, HEADER *hdr, BUFFER *cmd,
785                        int *err_continue)
786 {
787   char flags[LONG_STRING];
788   char uid[11];
789
790   hdr->changed = 0;
791
792   snprintf (uid, sizeof (uid), "%u", HEADER_DATA(hdr)->uid);
793   cmd->dptr = cmd->data;
794   mutt_buffer_addstr (cmd, "UID STORE ");
795   mutt_buffer_addstr (cmd, uid);
796
797   flags[0] = '\0';
798       
799   imap_set_flag (idata, ACL_SEEN, hdr->read, "\\Seen ",
800                  flags, sizeof (flags));
801   imap_set_flag (idata, ACL_WRITE, hdr->flagged,
802                  "\\Flagged ", flags, sizeof (flags));
803   imap_set_flag (idata, ACL_WRITE, hdr->replied,
804                  "\\Answered ", flags, sizeof (flags));
805   imap_set_flag (idata, ACL_DELETE, hdr->deleted,
806                  "\\Deleted ", flags, sizeof (flags));
807
808   /* now make sure we don't lose custom tags */
809   if (mutt_bit_isset (idata->rights, ACL_WRITE))
810     imap_add_keywords (flags, hdr, idata->flags, sizeof (flags));
811
812   m_strrtrim(flags);
813
814   /* UW-IMAP is OK with null flags, Cyrus isn't. The only solution is to
815    * explicitly revoke all system flags (if we have permission) */
816   if (!*flags)
817   {
818     imap_set_flag (idata, ACL_SEEN, 1, "\\Seen ", flags, sizeof (flags));
819     imap_set_flag (idata, ACL_WRITE, 1, "\\Flagged ", flags, sizeof (flags));
820     imap_set_flag (idata, ACL_WRITE, 1, "\\Answered ", flags, sizeof (flags));
821     imap_set_flag (idata, ACL_DELETE, 1, "\\Deleted ", flags, sizeof (flags));
822
823     m_strrtrim(flags);
824
825     mutt_buffer_addstr (cmd, " -FLAGS.SILENT (");
826   } else
827     mutt_buffer_addstr (cmd, " FLAGS.SILENT (");
828
829   mutt_buffer_addstr (cmd, flags);
830   mutt_buffer_addstr (cmd, ")");
831
832   /* dumb hack for bad UW-IMAP 4.7 servers spurious FLAGS updates */
833   hdr->active = 0;
834
835   /* after all this it's still possible to have no flags, if you
836    * have no ACL rights */
837   if (*flags && (imap_exec (idata, cmd->data, 0) != 0) &&
838       err_continue && (*err_continue != M_YES))
839   {
840     *err_continue = imap_continue ("imap_sync_message: STORE failed",
841                                    idata->cmd.buf.data);
842     if (*err_continue != M_YES)
843       return -1;
844   }
845
846   hdr->active = 1;
847   idata->ctx->changed--;
848
849   return 0;
850 }
851
852 /* update the IMAP server to reflect message changes done within mutt.
853  * Arguments
854  *   ctx: the current context
855  *   expunge: 0 or 1 - do expunge? 
856  */
857
858 int imap_sync_mailbox (CONTEXT * ctx, int expunge, int *index_hint)
859 {
860   IMAP_DATA *idata;
861   CONTEXT *appendctx = NULL;
862   BUFFER cmd;
863   int deleted;
864   int n;
865   int err_continue = M_NO;      /* continue on error? */
866   int rc;
867
868   idata = (IMAP_DATA *) ctx->data;
869
870   if (idata->state != IMAP_SELECTED) {
871     return -1;
872   }
873
874   /* This function is only called when the calling code expects the context
875    * to be changed. */
876   imap_allow_reopen (ctx);
877
878   if ((rc = imap_check_mailbox (ctx, index_hint, 0)) != 0)
879     return rc;
880
881   p_clear(&cmd, 1);
882
883   /* if we are expunging anyway, we can do deleted messages very quickly... */
884   if (expunge && mutt_bit_isset (idata->rights, ACL_DELETE)) {
885     mutt_buffer_addstr (&cmd, "UID STORE ");
886     deleted = imap_make_msg_set (idata, &cmd, M_DELETE, 1);
887
888     /* if we have a message set, then let's delete */
889     if (deleted) {
890       mutt_message (_("Marking %d messages deleted..."), deleted);
891       mutt_buffer_addstr (&cmd, " +FLAGS.SILENT (\\Deleted)");
892       /* mark these messages as unchanged so second pass ignores them. Done
893        * here so BOGUS UW-IMAP 4.7 SILENT FLAGS updates are ignored. */
894       for (n = 0; n < ctx->msgcount; n++)
895         if (ctx->hdrs[n]->deleted && ctx->hdrs[n]->changed)
896           ctx->hdrs[n]->active = 0;
897       if (imap_exec (idata, cmd.data, 0) != 0) {
898         mutt_error (_("Expunge failed"));
899         mutt_sleep (1);
900         rc = -1;
901         goto out;
902       }
903     }
904   }
905
906   /* save status changes */
907   for (n = 0; n < ctx->msgcount; n++) {
908     if (ctx->hdrs[n]->active && ctx->hdrs[n]->changed) {
909
910       mutt_message (_("Saving message status flags... [%d/%d]"), n + 1,
911                     ctx->msgcount);
912
913       /* if the message has been rethreaded or attachments have been deleted
914        * we delete the message and reupload it.
915        * This works better if we're expunging, of course. */
916       if ((ctx->hdrs[n]->env && (ctx->hdrs[n]->env->refs_changed || ctx->hdrs[n]->env->irt_changed)) ||
917           ctx->hdrs[n]->attach_del) {
918         if (!appendctx)
919           appendctx = mx_open_mailbox (ctx->path, M_APPEND | M_QUIET, NULL);
920         if (appendctx) {
921           _mutt_save_message (ctx->hdrs[n], appendctx, 1, 0, 0);
922         }
923       }
924
925       if (imap_sync_message (idata, ctx->hdrs[n], &cmd, &err_continue) < 0) {
926         rc = -1;
927         goto out;
928       }
929     }
930   }
931   ctx->changed = 0;
932
933   /* We must send an EXPUNGE command if we're not closing. */
934   if (expunge && !(ctx->closing) &&
935       mutt_bit_isset (idata->rights, ACL_DELETE)) {
936     mutt_message _("Expunging messages from server...");
937
938     /* Set expunge bit so we don't get spurious reopened messages */
939     idata->reopen |= IMAP_EXPUNGE_EXPECTED;
940     if (imap_exec (idata, "EXPUNGE", 0) != 0) {
941       imap_error (_("imap_sync_mailbox: EXPUNGE failed"), idata->cmd.buf.data);
942       rc = imap_reconnect (ctx);
943       goto out;
944     }
945   }
946
947   if (expunge && ctx->closing) {
948     if (imap_exec (idata, "CLOSE", 0))
949       mutt_error (_("CLOSE failed"));
950     idata->state = IMAP_AUTHENTICATED;
951   }
952
953   rc = 0;
954 out:
955   if (cmd.data)
956     p_delete(&cmd.data);
957   if (appendctx) {
958     mx_fastclose_mailbox (appendctx);
959     p_delete(&appendctx);
960   }
961   return rc;
962 }
963
964 /* imap_close_mailbox: clean up IMAP data in CONTEXT */
965 static void imap_close_mailbox (CONTEXT * ctx)
966 {
967   IMAP_DATA *idata;
968   int i;
969
970   idata = (IMAP_DATA *) ctx->data;
971   /* Check to see if the mailbox is actually open */
972   if (!idata)
973     return;
974
975   if (ctx == idata->ctx) {
976     if (idata->state != IMAP_FATAL && idata->state == IMAP_SELECTED) {
977       /* mx_close_mailbox won't sync if there are no deleted messages
978       * and the mailbox is unchanged, so we may have to close here */
979       if (!ctx->deleted && imap_exec (idata, "CLOSE", 0))
980         mutt_error (_("CLOSE failed"));
981       idata->state = IMAP_AUTHENTICATED;
982     }
983
984     idata->reopen &= IMAP_REOPEN_ALLOW;
985     p_delete(&(idata->mailbox));
986     string_list_wipe(&idata->flags);
987     idata->ctx = NULL;
988   }
989
990   /* free IMAP part of headers */
991   for (i = 0; i < ctx->msgcount; i++)
992     imap_free_header_data (&(ctx->hdrs[i]->data));
993
994   for (i = 0; i < IMAP_CACHE_LEN; i++) {
995     if (idata->cache[i].path) {
996       unlink (idata->cache[i].path);
997       p_delete(&idata->cache[i].path);
998     }
999   }
1000 }
1001
1002 /* use the NOOP command to poll for new mail
1003  *
1004  * return values:
1005  *      M_REOPENED      mailbox has been externally modified
1006  *      M_NEW_MAIL      new mail has arrived!
1007  *      0               no change
1008  *      -1              error
1009  */
1010 int imap_check_mailbox (CONTEXT * ctx, int *index_hint __attribute__ ((unused)), int force)
1011 {
1012   /* overload keyboard timeout to avoid many mailbox checks in a row.
1013    * Most users don't like having to wait exactly when they press a key. */
1014   IMAP_DATA *idata;
1015   int result = 0;
1016
1017   idata = (IMAP_DATA *) ctx->data;
1018
1019   if ((force || time (NULL) >= idata->lastread + Timeout)
1020       && imap_exec (idata, "NOOP", 0) != 0)
1021     return -1;
1022
1023   /* We call this even when we haven't run NOOP in case we have pending
1024    * changes to process, since we can reopen here. */
1025   imap_cmd_finish (idata);
1026
1027   if (idata->check_status & IMAP_EXPUNGE_PENDING)
1028     result = M_REOPENED;
1029   else if (idata->check_status & IMAP_NEWMAIL_PENDING)
1030     result = M_NEW_MAIL;
1031   else if (idata->check_status & IMAP_FLAGS_PENDING)
1032     result = M_FLAGS;
1033
1034   idata->check_status = 0;
1035
1036   return result;
1037 }
1038
1039 /*
1040  * count messages:
1041  *      new == 1:  recent
1042  *      new == 2:  unseen
1043  *      otherwise: total
1044  * return:
1045  *   0+   number of messages in mailbox
1046  *  -1    error while polling mailboxes
1047  */
1048 int imap_mailbox_check (char *path, int new)
1049 {
1050   CONNECTION *conn;
1051   IMAP_DATA *idata;
1052   char buf[LONG_STRING];
1053   char mbox[LONG_STRING];
1054   char mbox_unquoted[LONG_STRING];
1055   char *s;
1056   int msgcount = 0;
1057   int connflags = 0;
1058   IMAP_MBOX mx;
1059   int rc;
1060
1061   if (imap_parse_path (path, &mx))
1062     return -1;
1063
1064   /* If imap_passive is set, don't open a connection to check for new mail */
1065   if (option (OPTIMAPPASSIVE))
1066     connflags = M_IMAP_CONN_NONEW;
1067
1068   if (!(idata = imap_conn_find (&(mx.account), connflags))) {
1069     p_delete(&mx.mbox);
1070     return -1;
1071   }
1072   conn = idata->conn;
1073
1074   imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
1075   p_delete(&mx.mbox);
1076
1077   imap_munge_mbox_name (mbox, sizeof (mbox), buf);
1078   m_strcpy(mbox_unquoted, sizeof(mbox_unquoted), buf);
1079
1080   /* The draft IMAP implementor's guide warns againts using the STATUS
1081    * command on a mailbox that you have selected 
1082    */
1083
1084   if (m_strcmp(mbox_unquoted, idata->mailbox) == 0
1085       || (ascii_strcasecmp (mbox_unquoted, "INBOX") == 0
1086           && m_strcasecmp(mbox_unquoted, idata->mailbox) == 0)) {
1087     m_strcpy(buf, sizeof(buf), "NOOP");
1088   }
1089   else if (mutt_bit_isset (idata->capabilities, IMAP4REV1) ||
1090            mutt_bit_isset (idata->capabilities, STATUS)) {
1091     snprintf (buf, sizeof (buf), "STATUS %s (%s)", mbox,
1092               new == 1 ? "RECENT" : (new == 2 ? "UNSEEN" : "MESSAGES"));
1093   }
1094   else
1095     /* Server does not support STATUS, and this is not the current mailbox.
1096      * There is no lightweight way to check recent arrivals */
1097     return -1;
1098
1099   imap_cmd_start (idata, buf);
1100
1101   do {
1102     if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
1103       break;
1104
1105     s = imap_next_word (idata->cmd.buf.data);
1106     if (ascii_strncasecmp ("STATUS", s, 6) == 0) {
1107       s = imap_next_word (s);
1108       /* The mailbox name may or may not be quoted here. We could try to 
1109        * munge the server response and compare with quoted (or vise versa)
1110        * but it is probably more efficient to just strncmp against both. */
1111       if (m_strncmp(mbox_unquoted, s, m_strlen(mbox_unquoted)) == 0
1112           || m_strncmp(mbox, s, m_strlen(mbox)) == 0) {
1113         s = imap_next_word (s);
1114         s = imap_next_word (s);
1115         if (isdigit ((unsigned char) *s)) {
1116           if (*s != '0') {
1117             msgcount = atoi (s);
1118           }
1119         }
1120       }
1121     }
1122   }
1123   while (rc == IMAP_CMD_CONTINUE);
1124
1125   return msgcount;
1126 }
1127
1128 /* returns number of patterns in the search that should be done server-side
1129  * (eg are full-text) */
1130 static int do_search (const pattern_t* search, int allpats)
1131 {
1132   int rc = 0;
1133   const pattern_t* pat;
1134
1135   for (pat = search; pat; pat = pat->next) {
1136     switch (pat->op) {
1137       case M_BODY:
1138       case M_HEADER:
1139       case M_WHOLE_MSG:
1140         if (pat->stringmatch)
1141           rc++;
1142         break;
1143       default:
1144       if (pat->child && do_search (pat->child, 1))
1145         rc++;
1146     }
1147
1148     if (!allpats)
1149       break;
1150   }
1151
1152   return rc;
1153 }
1154
1155 /* convert mutt pattern_t to IMAP SEARCH command containing only elements
1156 * that require full-text search (mutt already has what it needs for most
1157 * match types, and does a better job (eg server doesn't support regexps). */
1158 static int imap_compile_search (const pattern_t* pat, BUFFER* buf)
1159 {
1160   if (! do_search (pat, 0))
1161     return 0;
1162
1163   if (pat->not)
1164     mutt_buffer_addstr (buf, "NOT ");
1165
1166   if (pat->child) {
1167     int clauses;
1168
1169     if ((clauses = do_search (pat->child, 1)) > 0) {
1170       const pattern_t* clause = pat->child;
1171
1172       mutt_buffer_addch (buf, '(');
1173
1174       while (clauses) {
1175         if (do_search (clause, 0)) {
1176           if (pat->op == M_OR && clauses > 1)
1177             mutt_buffer_addstr (buf, "OR ");
1178           clauses--;
1179           if (imap_compile_search (clause, buf) < 0)
1180             return -1;
1181
1182           if (clauses)
1183             mutt_buffer_addch (buf, ' ');
1184           
1185           clause = clause->next;
1186         }
1187       }
1188
1189       mutt_buffer_addch (buf, ')');
1190     }
1191   } else {
1192     char term[STRING];
1193     char *delim;
1194
1195     switch (pat->op) {
1196       case M_HEADER:
1197         mutt_buffer_addstr (buf, "HEADER ");
1198
1199         /* extract header name */
1200         if (! (delim = strchr (pat->str, ':'))) {
1201           mutt_error (_("Header search without header name: %s"), pat->str);
1202           return -1;
1203         }
1204         *delim = '\0';
1205         imap_quote_string (term, sizeof (term), pat->str);
1206         mutt_buffer_addstr (buf, term);
1207         mutt_buffer_addch (buf, ' ');
1208
1209         /* and field */
1210         *delim++ = ':';
1211         delim = vskipspaces(delim);
1212         imap_quote_string (term, sizeof (term), delim);
1213         mutt_buffer_addstr (buf, term);
1214         break;
1215
1216       case M_BODY:
1217         mutt_buffer_addstr (buf, "BODY ");
1218         imap_quote_string (term, sizeof (term), pat->str);
1219         mutt_buffer_addstr (buf, term);
1220         break;
1221
1222       case M_WHOLE_MSG:
1223         mutt_buffer_addstr (buf, "TEXT ");
1224         imap_quote_string (term, sizeof (term), pat->str);
1225         mutt_buffer_addstr (buf, term);
1226       break;
1227     }
1228   }
1229
1230   return 0;
1231 }
1232
1233 int imap_search (CONTEXT* ctx, const pattern_t* pat) {
1234   BUFFER buf;
1235   IMAP_DATA* idata = (IMAP_DATA*)ctx->data;
1236   int i;
1237
1238   for (i = 0; i < ctx->msgcount; i++)
1239     ctx->hdrs[i]->matched = 0;
1240
1241   if (!do_search (pat, 1))
1242     return 0;
1243
1244   p_clear(&buf, 1);
1245   mutt_buffer_addstr (&buf, "UID SEARCH ");
1246   if (imap_compile_search (pat, &buf) < 0) {
1247     p_delete(&buf.data);
1248     return -1;
1249   }
1250   if (imap_exec (idata, buf.data, 0) < 0) {
1251     p_delete(&buf.data);
1252     return -1;
1253   }
1254
1255   p_delete(&buf.data);
1256   return 0;
1257 }
1258
1259 /* all this listing/browsing is a mess. I don't like that name is a pointer
1260  *   into idata->buf (used to be a pointer into the passed in buffer, just
1261  *   as bad), nor do I like the fact that the fetch is done here. This
1262  *   code can't possibly handle non-string_list_t untagged responses properly.
1263  *   FIXME. ?! */
1264 int imap_parse_list_response (IMAP_DATA * idata, char **name, int *noselect,
1265                               int *noinferiors, char *delim)
1266 {
1267   char *s;
1268   long bytes;
1269   int rc;
1270
1271   *name = NULL;
1272
1273   rc = imap_cmd_step (idata);
1274   if (rc == IMAP_CMD_OK)
1275     return 0;
1276   if (rc != IMAP_CMD_CONTINUE)
1277     return -1;
1278
1279   s = imap_next_word (idata->cmd.buf.data);
1280   if ((ascii_strncasecmp ("LIST", s, 4) == 0) ||
1281       (ascii_strncasecmp ("LSUB", s, 4) == 0)) {
1282     *noselect = 0;
1283     *noinferiors = 0;
1284
1285     s = imap_next_word (s);     /* flags */
1286     if (*s == '(') {
1287       char *ep;
1288
1289       s++;
1290       ep = s;
1291       while (*ep && *ep != ')')
1292         ep++;
1293       do {
1294         if (!ascii_strncasecmp (s, "\\NoSelect", 9))
1295           *noselect = 1;
1296         if (!ascii_strncasecmp (s, "\\NoInferiors", 12))
1297           *noinferiors = 1;
1298         /* See draft-gahrns-imap-child-mailbox-?? */
1299         if (!ascii_strncasecmp (s, "\\HasNoChildren", 14))
1300           *noinferiors = 1;
1301         if (*s != ')')
1302           s++;
1303         while (*s && *s != '\\' && *s != ')')
1304           s++;
1305       } while (s != ep);
1306     }
1307     else
1308       return 0;
1309     s = imap_next_word (s);     /* delim */
1310     /* Reset the delimiter, this can change */
1311     if (ascii_strncasecmp (s, "NIL", 3)) {
1312       if (s && s[0] == '\"' && s[1] && s[2] == '\"')
1313         *delim = s[1];
1314       else if (s && s[0] == '\"' && s[1] && s[1] == '\\' && s[2]
1315                && s[3] == '\"')
1316         *delim = s[2];
1317     }
1318     s = imap_next_word (s);     /* name */
1319     if (s && *s == '{') {       /* Literal */
1320       if (imap_get_literal_count (idata->cmd.buf.data, &bytes) < 0)
1321         return -1;
1322       if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE)
1323         return -1;
1324       *name = idata->cmd.buf.data;
1325     }
1326     else
1327       *name = s;
1328   }
1329
1330   return 0;
1331 }
1332
1333 int imap_subscribe (char *path, int subscribe)
1334 {
1335   CONNECTION *conn;
1336   IMAP_DATA *idata;
1337   char buf[LONG_STRING];
1338   char mbox[LONG_STRING];
1339   IMAP_MBOX mx;
1340
1341   if (mx_get_magic (path) != M_IMAP || imap_parse_path (path, &mx) < 0) {
1342     mutt_error (_("Bad mailbox name"));
1343     return -1;
1344   }
1345
1346   if (!(idata = imap_conn_find (&(mx.account), 0)))
1347     goto fail;
1348
1349   conn = idata->conn;
1350
1351   imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
1352
1353   if (option (OPTIMAPCHECKSUBSCRIBED)) {
1354     buffy_do_mailboxes(path, subscribe);
1355   }
1356
1357   if (subscribe)
1358     mutt_message (_("Subscribing to %s..."), buf);
1359   else
1360     mutt_message (_("Unsubscribing to %s..."), buf);
1361   imap_munge_mbox_name (mbox, sizeof (mbox), buf);
1362
1363   snprintf (buf, sizeof (buf), "%sSUBSCRIBE %s", subscribe ? "" : "UN", mbox);
1364
1365   if (imap_exec (idata, buf, 0) < 0)
1366     goto fail;
1367
1368   p_delete(&mx.mbox);
1369   return 0;
1370
1371 fail:
1372   p_delete(&mx.mbox);
1373   return -1;
1374 }
1375
1376 /* trim dest to the length of the longest prefix it shares with src,
1377  * returning the length of the trimmed string */
1378 static int longest_common_prefix (char *dest, const char* src,
1379                                   int start, ssize_t dlen) {
1380   int pos = start;
1381
1382   while (pos < dlen && dest[pos] && dest[pos] == src[pos])
1383     pos++;
1384   dest[pos] = '\0';
1385
1386   return pos;
1387 }
1388
1389 /* look for IMAP URLs to complete from defined mailboxes. Could be extended
1390  * to complete over open connections and account/folder hooks too. */
1391 static int imap_complete_hosts (char *dest, ssize_t len) {
1392   BUFFY* mailbox;
1393   CONNECTION* conn;
1394   int rc = -1;
1395   int matchlen;
1396   int i = 0;
1397
1398   matchlen = m_strlen(dest);
1399   if (!Incoming.len)
1400     return (-1);
1401   for (i = 0; i < Incoming.len; i++) {
1402     mailbox = Incoming.arr[i];
1403     if (!m_strncmp(dest, mailbox->path, matchlen)) {
1404       if (rc) {
1405         m_strcpy(dest, len, mailbox->path);
1406         rc = 0;
1407       } else
1408         longest_common_prefix (dest, mailbox->path, matchlen, len);
1409     }
1410   }
1411
1412   for (conn = mutt_socket_head (); conn && conn->next; conn = conn->next) {
1413     ciss_url_t url;
1414     char urlstr[LONG_STRING];
1415
1416     if (conn->account.type != M_ACCT_TYPE_IMAP)
1417       continue;
1418
1419     mutt_account_tourl (&conn->account, &url);
1420     /* FIXME: how to handle multiple users on the same host? */
1421     url.user = NULL;
1422     url.path = NULL;
1423     url_ciss_tostring (&url, urlstr, sizeof (urlstr), 0);
1424     if (!m_strncmp(dest, urlstr, matchlen)) {
1425       if (rc) {
1426         m_strcpy(dest, len, urlstr);
1427         rc = 0;
1428       } else
1429         longest_common_prefix (dest, urlstr, matchlen, len);
1430     }
1431   }
1432
1433   return rc;
1434 }
1435
1436 /* imap_complete: given a partial IMAP folder path, return a string which
1437  *   adds as much to the path as is unique */
1438 int imap_complete (char *dest, size_t dlen, char *path) {
1439   CONNECTION *conn;
1440   IMAP_DATA *idata;
1441   char list[LONG_STRING];
1442   char buf[LONG_STRING];
1443   char *list_word = NULL;
1444   int noselect, noinferiors;
1445   char delim;
1446   char completion[LONG_STRING];
1447   int clen, matchlen = 0;
1448   int completions = 0;
1449   IMAP_MBOX mx;
1450
1451   if (imap_parse_path (path, &mx) || !mx.mbox) {
1452     m_strcpy(dest, dlen, path);
1453     return imap_complete_hosts (dest, dlen);
1454   }
1455
1456   /* don't open a new socket just for completion. Instead complete over
1457    * known mailboxes/hooks/etc */
1458   if (!(idata = imap_conn_find (&(mx.account), M_IMAP_CONN_NONEW))) {
1459     p_delete(&mx.mbox);
1460     m_strcpy(dest, dlen, path);
1461     return imap_complete_hosts (dest, dlen);
1462   }
1463   conn = idata->conn;
1464
1465   /* reformat path for IMAP list, and append wildcard */
1466   /* don't use INBOX in place of "" */
1467   if (mx.mbox && mx.mbox[0])
1468     imap_fix_path (idata, mx.mbox, list, sizeof (list));
1469   else
1470     list[0] = '\0';
1471
1472   /* fire off command */
1473   snprintf (buf, sizeof (buf), "%s \"\" \"%s%%\"",
1474             option (OPTIMAPLSUB) ? "LSUB" : "LIST", list);
1475
1476   imap_cmd_start (idata, buf);
1477
1478   /* and see what the results are */
1479   m_strcpy(completion, sizeof(completion), NONULL(mx.mbox));
1480   do {
1481     if (imap_parse_list_response (idata, &list_word, &noselect, &noinferiors,
1482                                   &delim))
1483       break;
1484
1485     if (list_word) {
1486       /* store unquoted */
1487       imap_unmunge_mbox_name (list_word);
1488
1489       /* if the folder isn't selectable, append delimiter to force browse
1490        * to enter it on second tab. */
1491       if (noselect) {
1492         clen = m_strlen(list_word);
1493         list_word[clen++] = delim;
1494         list_word[clen] = '\0';
1495       }
1496       /* copy in first word */
1497       if (!completions) {
1498         m_strcpy(completion, sizeof(completion), list_word);
1499         matchlen = m_strlen(completion);
1500         completions++;
1501         continue;
1502       }
1503
1504       matchlen = longest_common_prefix (completion, list_word, 0, matchlen);
1505       completions++;
1506     }
1507   }
1508   while (m_strncmp(idata->cmd.seq, idata->cmd.buf.data, SEQLEN));
1509
1510   if (completions) {
1511     /* reformat output */
1512     imap_qualify_path (dest, dlen, &mx, completion);
1513     mutt_pretty_mailbox (dest);
1514
1515     p_delete(&mx.mbox);
1516     return 0;
1517   }
1518
1519   return -1;
1520 }
1521
1522 /* reconnect if connection was lost */
1523 int imap_reconnect (CONTEXT * ctx)
1524 {
1525   IMAP_DATA *imap_data;
1526
1527   if (!ctx)
1528     return (-1);
1529
1530   imap_data = (IMAP_DATA *) ctx->data;
1531
1532   if (imap_data) {
1533     if (imap_data->status == IMAP_CONNECTED)
1534       return -1;
1535   }
1536
1537   if (query_quadoption
1538       (OPT_IMAPRECONNECT,
1539        _("Connection lost. Reconnect to IMAP server?")) != M_YES)
1540     return -1;
1541
1542   mx_open_mailbox (ctx->path, 0, ctx);
1543   return 0;
1544 }
1545
1546 int imap_is_magic (const char* path, struct stat* st __attribute__ ((unused))) {
1547   url_scheme_t s;
1548   if (!path || !*path)
1549     return (-1);
1550   s = url_check_scheme (NONULL (path));
1551   return ((s == U_IMAP || s == U_IMAPS) ? M_IMAP : -1);
1552 }
1553
1554 static int acl_check_imap (CONTEXT* ctx, int bit) {
1555   return (!mutt_bit_isset (((IMAP_DATA*) ctx->data)->capabilities, ACL) ||
1556           mutt_bit_isset (((IMAP_DATA*) ctx->data)->rights, bit));
1557 }
1558
1559 static int imap_open_new_message (MESSAGE * msg,
1560                                   CONTEXT * dest __attribute__ ((unused)),
1561                                   HEADER * hdr __attribute__ ((unused)))
1562 {
1563     char tmp[_POSIX_PATH_MAX];
1564
1565     msg->fp = m_tempfile(tmp, sizeof(tmp), NONULL(mod_core.tmpdir), NULL);
1566     if (!msg->fp) {
1567         mutt_perror(tmp);
1568         return -1;
1569     }
1570
1571     msg->path = m_strdup(tmp);
1572     return 0;
1573 }
1574
1575 /* this ugly kludge is required since the last int to
1576  * imap_check_mailbox() doesn't mean 'lock' but 'force'... */
1577 static int _imap_check_mailbox (CONTEXT* ctx,
1578                                 int* index_hint,
1579                                 int lock __attribute__ ((unused))) {
1580   return (imap_check_mailbox (ctx, index_hint, 0));
1581 }
1582
1583 static int imap_commit_message (MESSAGE* msg, CONTEXT* ctx) {
1584   int r = 0;
1585
1586   if ((r = m_fclose(&msg->fp)) == 0)
1587     r = imap_append_message (ctx, msg);
1588   return (r);
1589 }
1590
1591 mx_t const imap_mx = {
1592     M_IMAP,
1593     0,
1594     imap_is_magic,
1595     NULL,
1596     imap_access,
1597     imap_open_mailbox,
1598     imap_open_new_message,
1599     acl_check_imap,
1600     _imap_check_mailbox,
1601     imap_close_mailbox,
1602     imap_sync_mailbox,
1603     imap_commit_message,
1604 };