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