Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / providers / imap / camel-imap-utils.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Authors: Jeffrey Stedfast <fejj@ximian.com>
4  *
5  *  Copyright 2000 Ximian, Inc. (www.ximian.com)
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU Lesser General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  */
22
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <ctype.h>
29 #include <errno.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <time.h>
33 #include <unistd.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36
37 #include <glib.h>
38 #include <glib/gstdio.h>
39
40 #include "camel-folder.h"
41 #include "camel-string-utils.h"
42 #include "camel-utf8.h"
43
44 #include "camel-imap-store.h"
45 #include "camel-imap-summary.h"
46 #include "camel-imap-utils.h"
47
48 #define d(x)
49
50 #define SUBFOLDER_DIR_NAME     "subfolders"
51 #define SUBFOLDER_DIR_NAME_LEN 10
52
53 const char *
54 imap_next_word (const char *buf)
55 {
56         const char *word;
57         
58         /* skip over current word */
59         word = buf;
60         while (*word && *word != ' ')
61                 word++;
62         
63         /* skip over white space */
64         while (*word && *word == ' ')
65                 word++;
66         
67         return word;
68 }
69
70
71 static void
72 imap_namespace_destroy (struct _namespace *namespace)
73 {
74         struct _namespace *node, *next;
75         
76         node = namespace;
77         while (node) {
78                 next = node->next;
79                 g_free (node->prefix);
80                 g_free (node);
81                 node = next;
82         }
83 }
84
85 void
86 imap_namespaces_destroy (struct _namespaces *namespaces)
87 {
88         if (namespaces) {
89                 imap_namespace_destroy (namespaces->personal);
90                 imap_namespace_destroy (namespaces->other);
91                 imap_namespace_destroy (namespaces->shared);
92                 g_free (namespaces);
93         }
94 }
95
96 static gboolean
97 imap_namespace_decode (const char **in, struct _namespace **namespace)
98 {
99         struct _namespace *list, *tail, *node;
100         const char *inptr;
101         char *astring;
102         size_t len;
103         
104         inptr = *in;
105         
106         list = NULL;
107         tail = (struct _namespace *) &list;
108         
109         if (g_ascii_strncasecmp (inptr, "NIL", 3) != 0) {
110                 if (*inptr++ != '(')
111                         goto exception;
112                 
113                 while (*inptr && *inptr != ')') {
114                         if (*inptr++ != '(')
115                                 goto exception;
116                         
117                         node = g_new (struct _namespace, 1);
118                         node->next = NULL;
119                         
120                         /* get the namespace prefix */
121                         astring = imap_parse_astring (&inptr, &len);
122                         if (!astring) {
123                                 g_free (node);
124                                 goto exception;
125                         }
126                         
127                         /* decode IMAP's modified UTF-7 into UTF-8 */
128                         node->prefix = imap_mailbox_decode (astring, len);
129                         g_free (astring);
130                         if (!node->prefix) {
131                                 g_free (node);
132                                 goto exception;
133                         }
134                         
135                         tail->next = node;
136                         tail = node;
137                         
138                         /* get the namespace directory delimiter */
139                         inptr = imap_next_word (inptr);
140                         
141                         if (!g_ascii_strncasecmp (inptr, "NIL", 3)) {
142                                 inptr = imap_next_word (inptr);
143                                 node->delim = '\0';
144                         } else if (*inptr++ == '"') {
145                                 if (*inptr == '\\')
146                                         inptr++;
147                                 
148                                 node->delim = *inptr++;
149                                 
150                                 if (*inptr++ != '"')
151                                         goto exception;
152                         } else
153                                 goto exception;
154                         
155                         if (*inptr == ' ') {
156                                 /* parse extra flags... for now we
157                                    don't save them, but in the future
158                                    we may want to? */
159                                 while (*inptr == ' ')
160                                         inptr++;
161                                 
162                                 while (*inptr && *inptr != ')') {
163                                         /* this should be a QSTRING or ATOM */
164                                         inptr = imap_next_word (inptr);
165                                         if (*inptr == '(') {
166                                                 /* skip over the param list */
167                                                 imap_skip_list (&inptr);
168                                         }
169                                         
170                                         while (*inptr == ' ')
171                                                 inptr++;
172                                 }
173                         }
174                         
175                         if (*inptr++ != ')')
176                                 goto exception;
177                         
178                         /* there shouldn't be spaces according to the
179                            ABNF grammar, but we all know how closely
180                            people follow specs */
181                         while (*inptr == ' ')
182                                 inptr++;
183                 }
184                 
185                 if (*inptr == ')')
186                         inptr++;
187         } else {
188                 inptr += 3;
189         }
190         
191         *in = inptr;
192         *namespace = list;
193         
194         return TRUE;
195         
196  exception:
197         
198         /* clean up any namespaces we may have allocated */
199         imap_namespace_destroy (list);
200         
201         return FALSE;
202 }
203
204 #if d(!)0
205 static void
206 namespace_dump (struct _namespace *namespace)
207 {
208         struct _namespace *node;
209         
210         if (namespace) {
211                 printf ("(");
212                 node = namespace;
213                 while (node) {
214                         printf ("(\"%s\" ", node->prefix);
215                         if (node->delim)
216                                 printf ("\"%c\")", node->delim);
217                         else
218                                 printf ("NUL)");
219                         
220                         node = node->next;
221                         if (node)
222                                 printf (" ");
223                 }
224                 
225                 printf (")");
226         } else {
227                 printf ("NIL");
228         }
229 }
230
231 static void
232 namespaces_dump (struct _namespaces *namespaces)
233 {
234         printf ("namespace dump: ");
235         namespace_dump (namespaces->personal);
236         printf (" ");
237         namespace_dump (namespaces->other);
238         printf (" ");
239         namespace_dump (namespaces->shared);
240         printf ("\n");
241 }
242 #endif
243
244 struct _namespaces *
245 imap_parse_namespace_response (const char *response)
246 {
247         struct _namespaces *namespaces;
248         const char *inptr;
249         
250         d(printf ("parsing: %s\n", response));
251         
252         if (*response != '*')
253                 return NULL;
254         
255         inptr = imap_next_word (response);
256         if (g_ascii_strncasecmp (inptr, "NAMESPACE", 9) != 0)
257                 return NULL;
258         
259         inptr = imap_next_word (inptr);
260         
261         namespaces = g_new (struct _namespaces, 1);
262         namespaces->personal = NULL;
263         namespaces->other = NULL;
264         namespaces->shared = NULL;
265         
266         if (!imap_namespace_decode (&inptr, &namespaces->personal))
267                 goto exception;
268         
269         if (*inptr != ' ')
270                 goto exception;
271         
272         while (*inptr == ' ')
273                 inptr++;
274         
275         if (!imap_namespace_decode (&inptr, &namespaces->other))
276                 goto exception;
277         
278         if (*inptr != ' ')
279                 goto exception;
280         
281         while (*inptr == ' ')
282                 inptr++;
283         
284         if (!imap_namespace_decode (&inptr, &namespaces->shared))
285                 goto exception;
286         
287         d(namespaces_dump (namespaces));
288         
289         return namespaces;
290         
291  exception:
292         
293         imap_namespaces_destroy (namespaces);
294         
295         return NULL;
296 }
297
298 /**
299  * imap_parse_list_response:
300  * @store: the IMAP store whose list response we're parsing
301  * @buf: the LIST or LSUB response
302  * @flags: a pointer to a variable to store the flags in, or %NULL
303  * @sep: a pointer to a variable to store the hierarchy separator in, or %NULL
304  * @folder: a pointer to a variable to store the folder name in, or %NULL
305  *
306  * Parses a LIST or LSUB response and returns the desired parts of it.
307  * If @folder is non-%NULL, its value must be freed by the caller.
308  *
309  * Return value: whether or not the response was successfully parsed.
310  **/
311 gboolean
312 imap_parse_list_response (CamelImapStore *store, const char *buf, int *flags, char *sep, char **folder)
313 {
314         gboolean is_lsub = FALSE;
315         const char *word;
316         size_t len;
317         
318         if (*buf != '*')
319                 return FALSE;
320         
321         word = imap_next_word (buf);
322         if (g_ascii_strncasecmp (word, "LIST", 4) && g_ascii_strncasecmp (word, "LSUB", 4))
323                 return FALSE;
324         
325         /* check if we are looking at an LSUB response */
326         if (word[1] == 'S' || word[1] == 's')
327                 is_lsub = TRUE;
328         
329         /* get the flags */
330         word = imap_next_word (word);
331         if (*word != '(')
332                 return FALSE;
333         
334         if (flags)
335                 *flags = 0;
336         
337         word++;
338         while (*word != ')') {
339                 len = strcspn (word, " )");
340                 if (flags) {
341                         if (!g_ascii_strncasecmp (word, "\\NoInferiors", len))
342                                 *flags |= CAMEL_FOLDER_NOINFERIORS;
343                         else if (!g_ascii_strncasecmp (word, "\\NoSelect", len))
344                                 *flags |= CAMEL_FOLDER_NOSELECT;
345                         else if (!g_ascii_strncasecmp (word, "\\Marked", len))
346                                 *flags |= CAMEL_IMAP_FOLDER_MARKED;
347                         else if (!g_ascii_strncasecmp (word, "\\Unmarked", len))
348                                 *flags |= CAMEL_IMAP_FOLDER_UNMARKED;
349                         else if (!g_ascii_strncasecmp (word, "\\HasChildren", len))
350                                 *flags |= CAMEL_FOLDER_CHILDREN;
351                         else if (!g_ascii_strncasecmp (word, "\\HasNoChildren", len))
352                                 *flags |= CAMEL_FOLDER_NOCHILDREN;
353                 }
354                 
355                 word += len;
356                 while (*word == ' ')
357                         word++;
358         }
359         
360         /* get the directory separator */
361         word = imap_next_word (word);
362         if (!strncmp (word, "NIL", 3)) {
363                 if (sep)
364                         *sep = '\0';
365         } else if (*word++ == '"') {
366                 if (*word == '\\')
367                         word++;
368                 if (sep)
369                         *sep = *word;
370                 word++;
371                 if (*word++ != '"')
372                         return FALSE;
373         } else
374                 return FALSE;
375         
376         if (folder) {
377                 char *astring;
378                 char *mailbox;
379                 
380                 /* get the folder name */
381                 word = imap_next_word (word);
382                 astring = imap_parse_astring (&word, &len);
383                 if (!astring)
384                         return FALSE;
385
386                 *folder = astring;
387
388                 mailbox = imap_mailbox_decode (astring, strlen (astring));
389                 g_free (astring);
390                 if (!mailbox)
391                         return FALSE;
392                 
393                 /* Kludge around Courier imap's LSUB response for INBOX when it
394                  * isn't subscribed to.
395                  *
396                  * Ignore any \Noselect flags for INBOX when parsing
397                  * an LSUB response to work around the following response:
398                  *
399                  * * LSUB (\Noselect \HasChildren) "." "INBOX"
400                  *
401                  * Fixes bug #28929 (albeight in a very dodgy way imho, but what
402                  * can ya do when ya got the ignorance of marketing breathing
403                  * down your neck?)
404                  */
405                 if (is_lsub && flags && !g_ascii_strcasecmp (mailbox, "INBOX"))
406                         *flags &= ~CAMEL_FOLDER_NOSELECT;
407                 
408                 *folder = mailbox;
409
410         }
411         
412         return TRUE;
413 }
414
415
416 /**
417  * imap_parse_folder_name:
418  * @store:
419  * @folder_name:
420  *
421  * Return an array of folder paths representing the folder heirarchy.
422  * For example:
423  *   Full/Path/"to / from"/Folder
424  * Results in:
425  *   Full, Full/Path, Full/Path/"to / from", Full/Path/"to / from"/Folder
426  **/
427 char **
428 imap_parse_folder_name (CamelImapStore *store, const char *folder_name)
429 {
430         GPtrArray *heirarchy;
431         char **paths;
432         const char *p;
433         
434         p = folder_name;
435         if (*p == store->dir_sep)
436                 p++;
437         
438         heirarchy = g_ptr_array_new ();
439         
440         while (*p) {
441                 if (*p == '"') {
442                         p++;
443                         while (*p && *p != '"')
444                                 p++;
445                         if (*p)
446                                 p++;
447                         continue;
448                 }
449                 
450                 if (*p == store->dir_sep)
451                         g_ptr_array_add (heirarchy, g_strndup (folder_name, p - folder_name));
452                 
453                 p++;
454         }
455         
456         g_ptr_array_add (heirarchy, g_strdup (folder_name));
457         g_ptr_array_add (heirarchy, NULL);
458         
459         paths = (char **) heirarchy->pdata;
460         g_ptr_array_free (heirarchy, FALSE);
461         
462         return paths;
463 }
464
465 char *
466 imap_create_flag_list (guint32 flags)
467 {
468         GString *gstr;
469         char *flag_list;
470         
471         gstr = g_string_new ("(");
472
473         if (flags & CAMEL_MESSAGE_ANSWERED)
474                 g_string_append (gstr, "\\Answered ");
475         if (flags & CAMEL_MESSAGE_DELETED)
476                 g_string_append (gstr, "\\Deleted ");
477         if (flags & CAMEL_MESSAGE_DRAFT)
478                 g_string_append (gstr, "\\Draft ");
479         if (flags & CAMEL_MESSAGE_FLAGGED)
480                 g_string_append (gstr, "\\Flagged ");
481         if (flags & CAMEL_MESSAGE_SEEN)
482                 g_string_append (gstr, "\\Seen ");
483         if (flags & CAMEL_MESSAGE_JUNK)
484                 g_string_append (gstr, "Junk ");
485         if (flags & CAMEL_IMAP_MESSAGE_LABEL1)
486                 g_string_append(gstr, "$Label1 ");
487         if (flags & CAMEL_IMAP_MESSAGE_LABEL2)
488                 g_string_append(gstr, "$Label2 ");
489         if (flags & CAMEL_IMAP_MESSAGE_LABEL3)
490                 g_string_append(gstr, "$Label3 ");
491         if (flags & CAMEL_IMAP_MESSAGE_LABEL4)
492                 g_string_append(gstr, "$Label4 ");
493         if (flags & CAMEL_IMAP_MESSAGE_LABEL5)
494                 g_string_append(gstr, "$Label5 ");
495
496         if (gstr->str[gstr->len - 1] == ' ')
497                 gstr->str[gstr->len - 1] = ')';
498         else
499                 g_string_append_c (gstr, ')');
500
501         flag_list = gstr->str;
502         g_string_free (gstr, FALSE);
503         return flag_list;
504 }
505
506 guint32
507 imap_label_to_flags(CamelMessageInfo *info)
508 {
509         const char *label;
510         guint32 flags;
511
512         label = camel_message_info_user_tag(info, "label");
513         if (label == NULL)
514                 flags = 0;
515         else if (!strcmp(label, "important"))
516                 flags = CAMEL_IMAP_MESSAGE_LABEL1;
517         else if (!strcmp(label, "work"))
518                 flags =  CAMEL_IMAP_MESSAGE_LABEL2;
519         else if (!strcmp(label, "personal"))
520                 flags = CAMEL_IMAP_MESSAGE_LABEL3;
521         else if (!strcmp(label, "todo"))
522                 flags = CAMEL_IMAP_MESSAGE_LABEL4;
523         else if (!strcmp(label, "later"))
524                 flags = CAMEL_IMAP_MESSAGE_LABEL5;
525         else
526                 flags = 0;
527
528         return flags;
529 }
530
531 guint32
532 imap_parse_flag_list (char **flag_list_p)
533 {
534         char *flag_list = *flag_list_p;
535         guint32 flags = 0;
536         int len;
537         
538         if (*flag_list++ != '(') {
539                 *flag_list_p = NULL;
540                 return 0;
541         }
542         
543         while (*flag_list && *flag_list != ')') {
544                 len = strcspn (flag_list, " )");
545                 if (!g_ascii_strncasecmp (flag_list, "\\Answered", len))
546                         flags |= CAMEL_MESSAGE_ANSWERED;
547                 else if (!g_ascii_strncasecmp (flag_list, "\\Deleted", len))
548                         flags |= CAMEL_MESSAGE_DELETED;
549                 else if (!g_ascii_strncasecmp (flag_list, "\\Draft", len))
550                         flags |= CAMEL_MESSAGE_DRAFT;
551                 else if (!g_ascii_strncasecmp (flag_list, "\\Flagged", len))
552                         flags |= CAMEL_MESSAGE_FLAGGED;
553                 else if (!g_ascii_strncasecmp (flag_list, "\\Seen", len))
554                         flags |= CAMEL_MESSAGE_SEEN;
555                 else if (!g_ascii_strncasecmp (flag_list, "\\Recent", len))
556                         flags |= CAMEL_IMAP_MESSAGE_RECENT;
557                 else if (!g_ascii_strncasecmp(flag_list, "\\*", len))
558                         flags |= CAMEL_MESSAGE_USER|CAMEL_MESSAGE_JUNK|CAMEL_IMAP_MESSAGE_LABEL_MASK;
559                 else if (!g_ascii_strncasecmp(flag_list, "Junk", len))
560                         flags |= CAMEL_MESSAGE_JUNK;
561                 else if (!g_ascii_strncasecmp(flag_list, "$Label1", len))
562                         flags |= CAMEL_IMAP_MESSAGE_LABEL1;
563                 else if (!g_ascii_strncasecmp(flag_list, "$Label2", len))
564                         flags |= CAMEL_IMAP_MESSAGE_LABEL2;
565                 else if (!g_ascii_strncasecmp(flag_list, "$Label3", len))
566                         flags |= CAMEL_IMAP_MESSAGE_LABEL3;
567                 else if (!g_ascii_strncasecmp(flag_list, "$Label4", len))
568                         flags |= CAMEL_IMAP_MESSAGE_LABEL4;
569                 else if (!g_ascii_strncasecmp(flag_list, "$Label5", len))
570                         flags |= CAMEL_IMAP_MESSAGE_LABEL5;
571                 
572                 flag_list += len;
573                 if (*flag_list == ' ')
574                         flag_list++;
575         }
576         
577         if (*flag_list++ != ')') {
578                 *flag_list_p = NULL;
579                 return 0;
580         }
581         
582         *flag_list_p = flag_list;
583         return flags;
584 }
585
586 /*
587  From RFC3501, which adds resp_specials over RFC2060
588
589 ATOM_CHAR       ::= <any CHAR except atom_specials>
590
591 atom_specials   ::= "(" / ")" / "{" / SPACE / CTL / list_wildcards /
592                     quoted_specials / resp_secials
593
594 CHAR            ::= <any 7-bit US-ASCII character except NUL,
595                      0x01 - 0x7f>
596
597 CTL             ::= <any ASCII control character and DEL,
598                         0x00 - 0x1f, 0x7f>
599
600 SPACE           ::= <ASCII SP, space, 0x20>
601
602 list_wildcards  ::= "%" / "*"
603
604 quoted_specials ::= <"> / "\"
605
606 resp_specials   ::= "]"
607 */
608
609 static unsigned char imap_atom_specials[256] = {
610 /*      0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F */
611 /* 00 */0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
612 /* 10 */0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
613 /* 20 */0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,
614 /* 30 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
615 /* 40 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
616 /* 50 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1,
617 /* 60 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
618 /* 70 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
619         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
620         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
621         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
622         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
623         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
624         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
625         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
626         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
627 };
628
629 #define imap_is_atom_char(c) ((imap_atom_specials[(c)&0xff] & 0x01) != 0)
630
631 gboolean
632 imap_is_atom(const char *in)
633 {
634         register unsigned char c;
635         register const char *p = in;
636
637         while ((c = (unsigned char)*p)) {
638                 if (!imap_is_atom_char(c))
639                         return FALSE;
640                 p++;
641         }
642
643         /* check for empty string */
644         return p!=in;
645 }
646
647 /**
648  * imap_parse_string_generic:
649  * @str_p: a pointer to a string
650  * @len: a pointer to a size_t to return the length in
651  * @type: type of string (#IMAP_STRING, #IMAP_ASTRING, or #IMAP_NSTRING)
652  * to parse.
653  *
654  * This parses an IMAP "string" (quoted string or literal), "nstring"
655  * (NIL or string), or "astring" (atom or string) starting at *@str_p.
656  * On success, *@str_p will point to the first character after the end
657  * of the string, and *@len will contain the length of the returned
658  * string. On failure, *@str_p will be set to %NULL.
659  *
660  * This assumes that the string is in the form returned by
661  * camel_imap_command(): that line breaks are indicated by LF rather
662  * than CRLF.
663  *
664  * Return value: the parsed string, or %NULL if a NIL or no string
665  * was parsed. (In the former case, *@str_p will be %NULL; in the
666  * latter, it will point to the character after the NIL.)
667  **/
668 char *
669 imap_parse_string_generic (const char **str_p, size_t *len, int type)
670 {
671         const char *str = *str_p;
672         char *out;
673         
674         if (!str)
675                 return NULL;
676         else if (*str == '"') {
677                 char *p;
678                 size_t size;
679                 
680                 str++;
681                 size = strcspn (str, "\"") + 1;
682                 p = out = g_malloc (size);
683                 
684                 /* a quoted string cannot be broken into multiple lines */
685                 while (*str && *str != '"' && *str != '\n') {
686                         if (*str == '\\')
687                                 str++;
688                         *p++ = *str++;
689                         if (p - out == size) {
690                                 out = g_realloc (out, size * 2);
691                                 p = out + size;
692                                 size *= 2;
693                         }
694                 }
695                 if (*str != '"') {
696                         *str_p = NULL;
697                         g_free (out);
698                         return NULL;
699                 }
700                 *p = '\0';
701                 *str_p = str + 1;
702                 *len = strlen (out);
703                 return out;
704         } else if (*str == '{') {
705                 *len = strtoul (str + 1, (char **)&str, 10);
706                 if (*str++ != '}' || *str++ != '\n' || strlen (str) < *len) {
707                         *str_p = NULL;
708                         return NULL;
709                 }
710                 
711                 out = g_strndup (str, *len);
712                 *str_p = str + *len;
713                 return out;
714         } else if (type == IMAP_NSTRING && !g_ascii_strncasecmp (str, "nil", 3)) {
715                 *str_p += 3;
716                 *len = 0;
717                 return NULL;
718         } else if (type == IMAP_ASTRING && imap_is_atom_char ((unsigned char)*str)) {
719                 while (imap_is_atom_char ((unsigned char) *str))
720                         str++;
721                 
722                 *len = str - *str_p;
723                 out = g_strndup (*str_p, *len);
724                 *str_p += *len;
725                 return out;
726         } else {
727                 *str_p = NULL;
728                 return NULL;
729         }
730 }
731
732 static inline void
733 skip_char (const char **in, char ch)
734 {
735         if (*in && **in == ch)
736                 *in = *in + 1;
737         else
738                 *in = NULL;
739 }
740
741 /* Skip atom, string, or number */
742 static void
743 skip_asn (const char **str_p)
744 {
745         const char *str = *str_p;
746         
747         if (!str)
748                 return;
749         else if (*str == '"') {
750                 while (*++str && *str != '"') {
751                         if (*str == '\\') {
752                                 str++;
753                                 if (!*str)
754                                         break;
755                         }
756                 }
757                 if (*str == '"')
758                         *str_p = str + 1;
759                 else
760                         *str_p = NULL;
761         } else if (*str == '{') {
762                 unsigned long len;
763                 
764                 len = strtoul (str + 1, (char **) &str, 10);
765                 if (*str != '}' || *(str + 1) != '\n' ||
766                     strlen (str + 2) < len) {
767                         *str_p = NULL;
768                         return;
769                 }
770                 *str_p = str + 2 + len;
771         } else {
772                 /* We assume the string is well-formed and don't
773                  * bother making sure it's a valid atom.
774                  */
775                 while (*str && *str != ')' && *str != ' ')
776                         str++;
777                 *str_p = str;
778         }
779 }
780
781 void
782 imap_skip_list (const char **str_p)
783 {
784         skip_char (str_p, '(');
785         while (*str_p && **str_p != ')') {
786                 if (**str_p == '(')
787                         imap_skip_list (str_p);
788                 else
789                         skip_asn (str_p);
790                 if (*str_p && **str_p == ' ')
791                         skip_char (str_p, ' ');
792         }
793         skip_char (str_p, ')');
794 }
795
796 static int
797 parse_params (const char **parms_p, CamelContentType *type)
798 {
799         const char *parms = *parms_p;
800         char *name, *value;
801         size_t len;
802         
803         if (!g_ascii_strncasecmp (parms, "nil", 3)) {
804                 *parms_p += 3;
805                 return 0;
806         }
807         
808         if (*parms++ != '(')
809                 return -1;
810         
811         while (parms && *parms != ')') {
812                 name = imap_parse_nstring (&parms, &len);
813                 skip_char (&parms, ' ');
814                 value = imap_parse_nstring (&parms, &len);
815                 
816                 if (name && value)
817                         camel_content_type_set_param (type, name, value);
818                 g_free (name);
819                 g_free (value);
820                 
821                 if (parms && *parms == ' ')
822                         parms++;
823         }
824         
825         if (!parms || *parms++ != ')')
826                 return -1;
827         
828         *parms_p = parms;
829         
830         return 0;
831 }
832
833
834 static CamelMessageContentInfo *
835 imap_body_decode (const char **in, CamelMessageContentInfo *ci, CamelFolder *folder, GPtrArray *cis)
836 {
837         const char *inptr = *in;
838         CamelMessageContentInfo *child = NULL;
839         char *type, *subtype, *id = NULL;
840         CamelContentType *ctype = NULL;
841         char *description = NULL;
842         char *encoding = NULL;
843         size_t len;
844         size_t size;
845         char *p;
846         
847         if (*inptr++ != '(')
848                 return NULL;
849         
850         if (ci == NULL) {
851                 ci = camel_folder_summary_content_info_new (folder->summary);
852                 g_ptr_array_add (cis, ci);
853         }
854         
855         if (*inptr == '(') {
856                 /* body_type_mpart */
857                 CamelMessageContentInfo *tail, *children = NULL;
858                 
859                 tail = (CamelMessageContentInfo *) &children;
860                 
861                 do {
862                         if (!(child = imap_body_decode (&inptr, NULL, folder, cis)))
863                                 return NULL;
864                         
865                         child->parent = ci;
866                         tail->next = child;
867                         tail = child;
868                 } while (*inptr == '(');
869                 
870                 if (*inptr++ != ' ')
871                         return NULL;
872                 
873                 if (g_ascii_strncasecmp (inptr, "nil", 3) != 0) {
874                         subtype = imap_parse_string (&inptr, &len);
875                 } else {
876                         subtype = NULL;
877                         inptr += 3;
878                 }
879                 
880                 ctype = camel_content_type_new ("multipart", subtype ? subtype : "mixed");
881                 g_free (subtype);
882                 
883                 if (*inptr++ != ')') {
884                         camel_content_type_unref (ctype);
885                         return NULL;
886                 }
887                 
888                 ci->type = ctype;
889                 ci->childs = children;
890         } else {
891                 /* body_type_1part */
892                 if (g_ascii_strncasecmp (inptr, "nil", 3) != 0) {
893                         type = imap_parse_string (&inptr, &len);
894                         if (inptr == NULL)
895                                 return NULL;
896                 } else {
897                         return NULL;
898                 }
899                 
900                 if (*inptr++ != ' ') {
901                         g_free (type);
902                         return NULL;
903                 }
904                 
905                 if (g_ascii_strncasecmp (inptr, "nil", 3) != 0) {
906                         subtype = imap_parse_string (&inptr, &len);
907                         if (inptr == NULL) {
908                                 g_free (type);
909                                 return NULL;
910                         }
911                 } else {
912                         if (!g_ascii_strcasecmp (type, "text"))
913                                 subtype = g_strdup ("plain");
914                         else
915                                 subtype = NULL;
916                         inptr += 3;
917                 }
918                 
919                 camel_strdown (type);
920                 camel_strdown (subtype);
921                 ctype = camel_content_type_new (type, subtype);
922                 g_free (subtype);
923                 g_free (type);
924                 
925                 if (*inptr++ != ' ')
926                         goto exception;
927                 
928                 /* content-type params */
929                 if (parse_params (&inptr, ctype) == -1)
930                         goto exception;
931                 
932                 if (*inptr++ != ' ')
933                         goto exception;
934                 
935                 /* content-id */
936                 if (g_ascii_strncasecmp (inptr, "nil", 3) != 0) {
937                         id = imap_parse_string (&inptr, &len);
938                         if (inptr == NULL)
939                                 goto exception;
940                 } else
941                         inptr += 3;
942                 
943                 if (*inptr++ != ' ')
944                         goto exception;
945                 
946                 /* description */
947                 if (g_ascii_strncasecmp (inptr, "nil", 3) != 0) {
948                         description = imap_parse_string (&inptr, &len);
949                         if (inptr == NULL)
950                                 goto exception;
951                 } else
952                         inptr += 3;
953                 
954                 if (*inptr++ != ' ')
955                         goto exception;
956                 
957                 /* encoding */
958                 if (g_ascii_strncasecmp (inptr, "nil", 3) != 0) {
959                         encoding = imap_parse_string (&inptr, &len);
960                         if (inptr == NULL)
961                                 goto exception;
962                 } else
963                         inptr += 3;
964                 
965                 if (*inptr++ != ' ')
966                         goto exception;
967                 
968                 /* size */
969                 size = strtoul ((const char *) inptr, &p, 10);
970                 inptr = (const unsigned char *) p;
971                 
972                 if (camel_content_type_is (ctype, "message", "rfc822")) {
973                         /* body_type_msg */
974                         if (*inptr++ != ' ')
975                                 goto exception;
976                         
977                         /* envelope */
978                         imap_skip_list (&inptr);
979                         
980                         if (*inptr++ != ' ')
981                                 goto exception;
982                         
983                         /* body */
984                         if (!(child = imap_body_decode (&inptr, NULL, folder, cis)))
985                                 goto exception;
986                         child->parent = ci;
987                         
988                         if (*inptr++ != ' ')
989                                 goto exception;
990                         
991                         /* lines */
992                         strtoul ((const char *) inptr, &p, 10);
993                         inptr = (const unsigned char *) p;
994                 } else if (camel_content_type_is (ctype, "text", "*")) {
995                         if (*inptr++ != ' ')
996                                 goto exception;
997                         
998                         /* lines */
999                         strtoul ((const char *) inptr, &p, 10);
1000                         inptr = (const unsigned char *) p;
1001                 } else {
1002                         /* body_type_basic */
1003                 }
1004                 
1005                 if (*inptr++ != ')')
1006                         goto exception;
1007                 
1008                 ci->type = ctype;
1009                 ci->id = id;
1010                 ci->description = description;
1011                 ci->encoding = encoding;
1012                 ci->size = size;
1013                 ci->childs = child;
1014         }
1015         
1016         *in = inptr;
1017         
1018         return ci;
1019         
1020  exception:
1021         
1022         camel_content_type_unref (ctype);
1023         g_free (id);
1024         g_free (description);
1025         g_free (encoding);
1026         
1027         return NULL;
1028 }
1029
1030
1031 /**
1032  * imap_parse_body:
1033  * @body_p: pointer to the start of an IMAP "body"
1034  * @folder: an imap folder
1035  * @ci: a CamelMessageContentInfo to fill in
1036  *
1037  * This fills in @ci with data from *@body_p. On success *@body_p
1038  * will point to the character after the body. On failure, it will be
1039  * set to %NULL and @ci will be unchanged.
1040  **/
1041 void
1042 imap_parse_body (const char **body_p, CamelFolder *folder,
1043                  CamelMessageContentInfo *ci)
1044 {
1045         const char *inptr = *body_p;
1046         CamelMessageContentInfo *child;
1047         GPtrArray *children;
1048         int i;
1049         
1050         if (!inptr || *inptr != '(') {
1051                 *body_p = NULL;
1052                 return;
1053         }
1054         
1055         children = g_ptr_array_new ();
1056         
1057         if (!(imap_body_decode (&inptr, ci, folder, children))) {
1058                 for (i = 0; i < children->len; i++) {
1059                         child = children->pdata[i];
1060                         
1061                         /* content_info_free will free all the child
1062                          * nodes, but we don't want that. */
1063                         child->next = NULL;
1064                         child->parent = NULL;
1065                         child->childs = NULL;
1066                         
1067                         camel_folder_summary_content_info_free (folder->summary, child);
1068                 }
1069                 *body_p = NULL;
1070         } else {
1071                 *body_p = inptr;
1072         }
1073         
1074         g_ptr_array_free (children, TRUE);
1075 }
1076
1077
1078 /**
1079  * imap_quote_string:
1080  * @str: the string to quote, which must not contain CR or LF
1081  *
1082  * Return value: an IMAP "quoted" corresponding to the string, which
1083  * the caller must free.
1084  **/
1085 char *
1086 imap_quote_string (const char *str)
1087 {
1088         const char *p;
1089         char *quoted, *q;
1090         int len;
1091         
1092         g_assert (strchr (str, '\r') == NULL);
1093         
1094         len = strlen (str);
1095         p = str;
1096         while ((p = strpbrk (p, "\"\\"))) {
1097                 len++;
1098                 p++;
1099         }
1100         
1101         quoted = q = g_malloc (len + 3);
1102         *q++ = '"';
1103         for (p = str; *p; ) {
1104                 if (strchr ("\"\\", *p))
1105                         *q++ = '\\';
1106                 *q++ = *p++;
1107         }
1108         *q++ = '"';
1109         *q = '\0';
1110         
1111         return quoted;
1112 }
1113
1114
1115 static inline unsigned long
1116 get_summary_uid_numeric (CamelFolderSummary *summary, int index)
1117 {
1118         CamelMessageInfo *info;
1119         unsigned long uid;
1120         
1121         info = camel_folder_summary_index (summary, index);
1122         uid = strtoul (camel_message_info_uid (info), NULL, 10);
1123         camel_message_info_free(info);
1124         return uid;
1125 }
1126
1127 /* the max number of chars that an unsigned 32-bit int can be is 10 chars plus 1 for a possible : */
1128 #define UID_SET_FULL(setlen, maxlen) (maxlen > 0 ? setlen + 11 >= maxlen : FALSE)
1129
1130 /**
1131  * imap_uid_array_to_set:
1132  * @summary: summary for the folder the UIDs come from
1133  * @uids: a (sorted) array of UIDs
1134  * @uid: uid index to start at
1135  * @maxlen: max length of the set string (or -1 for infinite)
1136  * @lastuid: index offset of the last uid used
1137  *
1138  * Creates an IMAP "set" up to @maxlen bytes long, covering the listed
1139  * UIDs starting at index @uid and not covering any UIDs that are in
1140  * @summary but not in @uids. It doesn't actually require that all (or
1141  * any) of the UIDs be in @summary.
1142  *
1143  * After calling, @lastuid will be set the index of the first uid
1144  * *not* included in the returned set string.
1145  *
1146  * Note: @uids MUST be in sorted order for this code to work properly.
1147  * 
1148  * Return value: the set, which the caller must free with g_free()
1149  **/
1150 char *
1151 imap_uid_array_to_set (CamelFolderSummary *summary, GPtrArray *uids, int uid, ssize_t maxlen, int *lastuid)
1152 {
1153         unsigned long last_uid, next_summary_uid, this_uid;
1154         gboolean range = FALSE;
1155         int si, scount;
1156         GString *gset;
1157         char *set;
1158         
1159         g_return_val_if_fail (uids->len > uid, NULL);
1160         
1161         gset = g_string_new (uids->pdata[uid]);
1162         last_uid = strtoul (uids->pdata[uid], NULL, 10);
1163         next_summary_uid = 0;
1164         scount = camel_folder_summary_count (summary);
1165         
1166         for (uid++, si = 0; uid < uids->len && !UID_SET_FULL (gset->len, maxlen); uid++) {
1167                 /* Find the next UID in the summary after the one we
1168                  * just wrote out.
1169                  */
1170                 for ( ; last_uid >= next_summary_uid && si < scount; si++)
1171                         next_summary_uid = get_summary_uid_numeric (summary, si);
1172                 if (last_uid >= next_summary_uid)
1173                         next_summary_uid = (unsigned long) -1;
1174                 
1175                 /* Now get the next UID from @uids */
1176                 this_uid = strtoul (uids->pdata[uid], NULL, 10);
1177                 if (this_uid == next_summary_uid || this_uid == last_uid + 1)
1178                         range = TRUE;
1179                 else {
1180                         if (range) {
1181                                 g_string_append_printf (gset, ":%lu", last_uid);
1182                                 range = FALSE;
1183                         }
1184                         g_string_append_printf (gset, ",%lu", this_uid);
1185                 }
1186                 
1187                 last_uid = this_uid;
1188         }
1189         
1190         if (range)
1191                 g_string_append_printf (gset, ":%lu", last_uid);
1192         
1193         *lastuid = uid;
1194         
1195         set = gset->str;
1196         g_string_free (gset, FALSE);
1197         
1198         return set;
1199 }
1200
1201 /**
1202  * imap_uid_set_to_array:
1203  * @summary: summary for the folder the UIDs come from
1204  * @uids: a pointer to the start of an IMAP "set" of UIDs
1205  *
1206  * Fills an array with the UIDs corresponding to @uids and @summary.
1207  * There can be text after the uid set in @uids, which will be
1208  * ignored.
1209  *
1210  * If @uids specifies a range of UIDs that extends outside the range
1211  * of @summary, the function will assume that all of the "missing" UIDs
1212  * do exist.
1213  *
1214  * Return value: the array of uids, which the caller must free with
1215  * imap_uid_array_free(). (Or %NULL if the uid set can't be parsed.)
1216  **/
1217 GPtrArray *
1218 imap_uid_set_to_array (CamelFolderSummary *summary, const char *uids)
1219 {
1220         GPtrArray *arr;
1221         char *p, *q;
1222         unsigned long uid, suid;
1223         int si, scount;
1224         
1225         arr = g_ptr_array_new ();
1226         scount = camel_folder_summary_count (summary);
1227         
1228         p = (char *)uids;
1229         si = 0;
1230         do {
1231                 uid = strtoul (p, &q, 10);
1232                 if (p == q)
1233                         goto lose;
1234                 g_ptr_array_add (arr, g_strndup (p, q - p));
1235                 
1236                 if (*q == ':') {
1237                         /* Find the summary entry for the UID after the one
1238                          * we just saw.
1239                          */
1240                         while (++si < scount) {
1241                                 suid = get_summary_uid_numeric (summary, si);
1242                                 if (suid > uid)
1243                                         break;
1244                         }
1245                         if (si >= scount)
1246                                 suid = uid + 1;
1247                         
1248                         uid = strtoul (q + 1, &p, 10);
1249                         if (p == q + 1)
1250                                 goto lose;
1251                         
1252                         /* Add each summary UID until we find one
1253                          * larger than the end of the range
1254                          */
1255                         while (suid <= uid) {
1256                                 g_ptr_array_add (arr, g_strdup_printf ("%lu", suid));
1257                                 if (++si < scount)
1258                                         suid = get_summary_uid_numeric (summary, si);
1259                                 else
1260                                         suid++;
1261                         }
1262                 } else
1263                         p = q;
1264         } while (*p++ == ',');
1265         
1266         return arr;
1267         
1268  lose:
1269         g_warning ("Invalid uid set %s", uids);
1270         imap_uid_array_free (arr);
1271         return NULL;
1272 }
1273
1274 /**
1275  * imap_uid_array_free:
1276  * @arr: an array returned from imap_uid_set_to_array()
1277  *
1278  * Frees @arr
1279  **/
1280 void
1281 imap_uid_array_free (GPtrArray *arr)
1282 {
1283         int i;
1284         
1285         for (i = 0; i < arr->len; i++)
1286                 g_free (arr->pdata[i]);
1287         g_ptr_array_free (arr, TRUE);
1288 }
1289
1290 char *
1291 imap_concat (CamelImapStore *imap_store, const char *prefix, const char *suffix)
1292 {
1293         size_t len;
1294         
1295         len = strlen (prefix);
1296         if (len == 0 || prefix[len - 1] == imap_store->dir_sep)
1297                 return g_strdup_printf ("%s%s", prefix, suffix);
1298         else
1299                 return g_strdup_printf ("%s%c%s", prefix, imap_store->dir_sep, suffix);
1300 }
1301
1302 char *
1303 imap_mailbox_encode (const unsigned char *in, size_t inlen)
1304 {
1305         char *buf;
1306
1307         buf = g_alloca (inlen + 1);
1308         memcpy (buf, in, inlen);
1309         buf[inlen] = 0;
1310         
1311         return camel_utf8_utf7 (buf);
1312 }
1313
1314 char *
1315 imap_mailbox_decode (const unsigned char *in, size_t inlen)
1316 {
1317         char *buf;
1318         
1319         buf = g_alloca (inlen + 1);
1320         memcpy (buf, in, inlen);
1321         buf[inlen] = 0;
1322         
1323         return camel_utf7_utf8 (buf);
1324 }
1325
1326 char *
1327 imap_path_to_physical (const char *prefix, const char *vpath)
1328 {
1329         GString *out = g_string_new(prefix);
1330         const char *p = vpath;
1331         char c, *res;
1332
1333         g_string_append_c(out, '/');
1334         p = vpath;
1335         while ((c = *p++)) {
1336                 if (c == '/') {
1337                         g_string_append(out, "/" SUBFOLDER_DIR_NAME "/");
1338                         while (*p == '/')
1339                                 p++;
1340                 } else
1341                         g_string_append_c(out, c);
1342         }
1343
1344         res = out->str;
1345         g_string_free(out, FALSE);
1346
1347         return res;
1348 }
1349
1350 static gboolean
1351 find_folders_recursive (const char *physical_path, const char *path,
1352                         IMAPPathFindFoldersCallback callback, gpointer data)
1353 {
1354         GDir *dir;
1355         char *subfolder_directory_path;
1356         gboolean ok;
1357
1358         if (*path) {
1359                 if (!callback (physical_path, path, data))
1360                         return FALSE;
1361
1362                 subfolder_directory_path = g_strdup_printf ("%s/%s", physical_path, SUBFOLDER_DIR_NAME);
1363         } else {
1364                 /* On the top level, we have no folders and,
1365                  * consequently, no subfolder directory.
1366                  */
1367
1368                 subfolder_directory_path = g_strdup (physical_path);
1369         }
1370
1371         /* Now scan the subfolders and load them. */
1372         dir = g_dir_open (subfolder_directory_path, 0, NULL);
1373         if (dir == NULL) {
1374                 g_free (subfolder_directory_path);
1375                 return TRUE;
1376         }
1377
1378         ok = TRUE;
1379         while (ok) {
1380                 struct stat file_stat;
1381                 const char *dirent;
1382                 char *file_path;
1383                 char *new_path;
1384
1385                 dirent = g_dir_read_name (dir);
1386                 if (dirent == NULL)
1387                         break;
1388
1389                 file_path = g_strdup_printf ("%s/%s", subfolder_directory_path,
1390                                              dirent);
1391
1392                 if (g_stat (file_path, &file_stat) < 0 ||
1393                     ! S_ISDIR (file_stat.st_mode)) {
1394                         g_free (file_path);
1395                         continue;
1396                 }
1397
1398                 new_path = g_strdup_printf ("%s/%s", path, dirent);
1399
1400                 ok = find_folders_recursive (file_path, new_path, callback, data);
1401
1402                 g_free (file_path);
1403                 g_free (new_path);
1404         }
1405
1406         g_dir_close (dir);
1407         g_free (subfolder_directory_path);
1408
1409         return ok;
1410 }
1411
1412 /**
1413  * imap_path_find_folders:
1414  * @prefix: directory to start from
1415  * @callback: Callback to invoke on each folder
1416  * @data: Data for @callback
1417  *
1418  * Walks the folder tree starting at @prefix and calls @callback
1419  * on each folder.
1420  *
1421  * Return value: %TRUE on success, %FALSE if an error occurs at any point
1422  **/
1423 gboolean
1424 imap_path_find_folders (const char *prefix, IMAPPathFindFoldersCallback callback, gpointer data)
1425 {
1426         return find_folders_recursive (prefix, "", callback, data);
1427 }