Bug #606181 - Accepting bad SSL certificate applies to any hostname
[platform/upstream/evolution-data-server.git] / camel / providers / imapx / camel-imapx-utils.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU Lesser General Public
7  * License as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include <ctype.h>
21 #include <errno.h>
22 #include <string.h>
23
24 #include "camel-imapx-folder.h"
25 #include "camel-imapx-settings.h"
26 #include "camel-imapx-stream.h"
27 #include "camel-imapx-summary.h"
28 #include "camel-imapx-store.h"
29 #include "camel-imapx-store-summary.h"
30 #include "camel-imapx-utils.h"
31
32 /* high-level parser state */
33 #define p(...) camel_imapx_debug(parse, __VA_ARGS__)
34 /* debug */
35 #define d(...) camel_imapx_debug(debug, __VA_ARGS__)
36
37 gint camel_imapx_debug_flags;
38 extern gint camel_verbose_debug;
39
40 #define debug_set_flag(flag) do { \
41         if ((CAMEL_IMAPX_DEBUG_ALL & CAMEL_IMAPX_DEBUG_ ## flag) &&     \
42             camel_debug("imapx:" #flag))                                \
43                 camel_imapx_debug_flags |= CAMEL_IMAPX_DEBUG_ ## flag;  \
44         } while (0)
45
46 static void camel_imapx_set_debug_flags (void)
47 {
48         if (camel_verbose_debug || camel_debug("imapx")) {
49                 camel_imapx_debug_flags = CAMEL_IMAPX_DEBUG_ALL;
50                 return;
51         }
52
53         debug_set_flag (command);
54         debug_set_flag (debug);
55         debug_set_flag (extra);
56         debug_set_flag (io);
57         debug_set_flag (token);
58         debug_set_flag (parse);
59         debug_set_flag (conman);
60 }
61
62 #include "camel-imapx-tokenise.h"
63 #define SUBFOLDER_DIR_NAME     "subfolders"
64
65 #ifdef __GNUC__
66 __inline
67 #endif
68 camel_imapx_id_t
69 imapx_tokenise (register const gchar *str,
70                 register guint len)
71 {
72         struct _imapx_keyword *k = imapx_tokenise_struct (str, len);
73
74         if (k)
75                 return k->id;
76         return 0;
77 }
78
79 static void imapx_namespace_clear (CamelIMAPXStoreNamespace **ns);
80 static const gchar * rename_label_flag (const gchar *flag, gint len, gboolean server_to_evo);
81
82 /* flag table */
83 static struct {
84         const gchar *name;
85         guint32 flag;
86 } flag_table[] = {
87         { "\\ANSWERED", CAMEL_MESSAGE_ANSWERED },
88         { "\\DELETED", CAMEL_MESSAGE_DELETED },
89         { "\\DRAFT", CAMEL_MESSAGE_DRAFT },
90         { "\\FLAGGED", CAMEL_MESSAGE_FLAGGED },
91         { "\\SEEN", CAMEL_MESSAGE_SEEN },
92         { "\\RECENT", CAMEL_IMAPX_MESSAGE_RECENT },
93         { "JUNK", CAMEL_MESSAGE_JUNK },
94         { "NOTJUNK", CAMEL_MESSAGE_NOTJUNK },
95         { "\\*", CAMEL_MESSAGE_USER }
96 };
97
98 /* utility functions
99  * should this be part of imapx-driver? */
100 /* maybe this should be a stream op? */
101 void
102 imapx_parse_flags (CamelIMAPXStream *stream,
103                    guint32 *flagsp,
104                    CamelFlag **user_flagsp,
105                    GCancellable *cancellable,
106                    GError **error)
107 /* throws IO,PARSE exception */
108 {
109         gint tok, i;
110         guint len;
111         guchar *token;
112         guint32 flags = 0;
113
114         *flagsp = flags;
115
116         tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
117         if (tok == '(') {
118                 do {
119                         tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
120                         if (tok == IMAPX_TOK_TOKEN || tok == IMAPX_TOK_INT) {
121                                 gchar *upper = g_ascii_strup ((gchar *) token, len);
122
123                                 for (i = 0; i < G_N_ELEMENTS (flag_table); i++)
124                                         if (!strcmp (upper, flag_table[i].name)) {
125                                                 flags |= flag_table[i].flag;
126                                                 goto found;
127                                         }
128                                 if (user_flagsp) {
129                                         const gchar *flag_name = rename_label_flag ((gchar *) token, strlen ((gchar *) token), TRUE);
130
131                                         camel_flag_set (user_flagsp, flag_name, TRUE);
132
133                                 }
134                         found:
135                                 tok = tok; /* fixes stupid warning */
136                                 g_free (upper);
137                         } else if (tok != ')') {
138                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting flag");
139                                 return;
140                         }
141                 } while (tok != ')');
142         } else {
143                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "execting flag list");
144                 return;
145         }
146
147         *flagsp = flags;
148 }
149
150 /*
151  * rename_flag
152  * Converts label flag name on server to name used in Evolution or back.
153  * if the flags does not match returns the original one as it is.
154  * It will never return NULL, it will return empty string, instead.
155  *
156  * @flag: Flag to rename.
157  * @len: Length of the flag name.
158  * @server_to_evo: if TRUE, then converting server names to evo's names, if FALSE then opposite.
159  */
160 static const gchar *
161 rename_label_flag (const gchar *flag,
162                    gint len,
163                    gboolean server_to_evo)
164 {
165         gint i;
166         const gchar *labels[] = {
167                 "$Label1", "$Labelimportant",
168                 "$Label2", "$Labelwork",
169                 "$Label3", "$Labelpersonal",
170                 "$Label4", "$Labeltodo",
171                 "$Label5", "$Labellater",
172                 NULL,      NULL };
173
174         /* It really can pass zero-length flags inside, in that case it was able
175          * to always add first label, which is definitely wrong. */
176         if (!len || !flag || !*flag)
177                 return "";
178
179         for (i = 0 + (server_to_evo ? 0 : 1); labels[i]; i = i + 2) {
180                 if (!g_ascii_strncasecmp (flag, labels[i], len))
181                         return labels[i + (server_to_evo ? 1 : -1)];
182         }
183
184         return flag;
185 }
186
187 void
188 imapx_write_flags (GString *string,
189                    guint32 flags,
190                    CamelFlag *user_flags)
191 {
192         gint i;
193         gboolean first = TRUE;
194
195         g_string_append_c (string, '(');
196
197         for (i = 0; flags != 0 && i< G_N_ELEMENTS (flag_table); i++) {
198                 if (flag_table[i].flag & flags) {
199                         if (flags & CAMEL_IMAPX_MESSAGE_RECENT)
200                                 continue;
201                         if (!first)
202                                 g_string_append_c (string, ' ');
203                         first = FALSE;
204                         g_string_append (string, flag_table[i].name);
205
206                         flags &= ~flag_table[i].flag;
207                 }
208         }
209
210         while (user_flags) {
211                 const gchar *flag_name;
212
213                 flag_name = rename_label_flag (
214                         user_flags->name, strlen (user_flags->name), FALSE);
215
216                 if (!first)
217                         g_string_append_c (string, ' ');
218                 first = FALSE;
219                 g_string_append (string, flag_name);
220
221                 user_flags = user_flags->next;
222         }
223
224         g_string_append_c (string, ')');
225 }
226
227 static gboolean
228 imapx_update_user_flags (CamelMessageInfo *info,
229                          CamelFlag *server_user_flags)
230 {
231         gboolean changed = FALSE;
232         CamelMessageInfoBase *binfo = (CamelMessageInfoBase *) info;
233         CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) info;
234         gboolean set_cal = FALSE;
235
236         if (camel_flag_get (&binfo->user_flags, "$has_cal"))
237                 set_cal = TRUE;
238
239         changed = camel_flag_list_copy (&binfo->user_flags, &server_user_flags);
240         camel_flag_list_copy (&xinfo->server_user_flags, &server_user_flags);
241
242         /* reset the calendar flag if it was set in messageinfo before */
243         if (set_cal)
244                 camel_flag_set (&binfo->user_flags, "$has_cal", TRUE);
245
246         return changed;
247 }
248
249 gboolean
250 imapx_update_message_info_flags (CamelMessageInfo *info,
251                                  guint32 server_flags,
252                                  CamelFlag *server_user_flags,
253                                  guint32 permanent_flags,
254                                  CamelFolder *folder,
255                                  gboolean unsolicited)
256 {
257         gboolean changed = FALSE;
258         CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) info;
259
260         if (server_flags != xinfo->server_flags) {
261                 guint32 server_set, server_cleared;
262
263                 server_set = server_flags & ~xinfo->server_flags;
264                 server_cleared = xinfo->server_flags & ~server_flags;
265
266                 /* Don't clear non-permanent server-side flags.
267                  * This avoids overwriting local flags that we
268                  * do store permanently, such as junk flags. */
269                 if (permanent_flags > 0)
270                         server_cleared &= permanent_flags;
271
272                 camel_message_info_set_flags ((CamelMessageInfo *) xinfo, server_set | server_cleared, (xinfo->info.flags | server_set) & ~server_cleared);
273
274                 xinfo->server_flags = server_flags;
275                 xinfo->info.flags = xinfo->info.flags & ~CAMEL_MESSAGE_FOLDER_FLAGGED;
276                 xinfo->info.dirty = TRUE;
277
278                 changed = TRUE;
279         }
280
281         if ((folder->permanent_flags & CAMEL_MESSAGE_USER) != 0 && imapx_update_user_flags (info, server_user_flags))
282                 changed = TRUE;
283
284         return changed;
285 }
286
287 void
288 imapx_set_message_info_flags_for_new_message (CamelMessageInfo *info,
289                                               guint32 server_flags,
290                                               CamelFlag *server_user_flags,
291                                               CamelFolder *folder)
292 {
293         CamelMessageInfoBase *binfo = (CamelMessageInfoBase *) info;
294         CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) info;
295
296         binfo->flags |= server_flags;
297         camel_message_info_set_flags (info, server_flags, binfo->flags | server_flags);
298
299         xinfo->server_flags = server_flags;
300
301         if (folder->permanent_flags & CAMEL_MESSAGE_USER)
302                 imapx_update_user_flags (info, server_user_flags);
303
304         binfo->flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
305         binfo->dirty = TRUE;
306 }
307
308 void
309 imapx_update_store_summary (CamelFolder *folder)
310 {
311         CamelStoreInfo *si;
312         CamelStore *parent_store;
313         const gchar *full_name;
314         CamelService *service;
315         CamelSettings *settings;
316         gboolean mobile_mode;
317
318         full_name = camel_folder_get_full_name (folder);
319         parent_store = camel_folder_get_parent_store (folder);
320         service = CAMEL_SERVICE (parent_store);
321         settings = camel_service_get_settings (service);
322         mobile_mode = camel_imapx_settings_get_mobile_mode (
323                 CAMEL_IMAPX_SETTINGS (settings));
324
325         si = camel_store_summary_path ((CamelStoreSummary *) ((CamelIMAPXStore *) parent_store)->summary, full_name);
326         if (si) {
327                 guint32 unread, total;
328
329                 total = camel_folder_summary_count (folder->summary);
330                 unread = camel_folder_summary_get_unread_count (folder->summary);
331
332                 if (si->unread != unread || si->total != total) {
333
334                         if (!mobile_mode)
335                                 si->unread = unread;
336                         else
337                                 si->unread =  ((CamelIMAPXFolder *) folder)->unread_on_server;
338                         si->total = total;
339
340                         camel_store_summary_touch ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
341                         camel_store_summary_save ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
342                 }
343         }
344 }
345
346 /*
347  * capability_data ::= "CAPABILITY" SPACE [1#capability SPACE] "IMAP4rev1"
348  *                     [SPACE 1#capability]
349  *                 ;; IMAP4rev1 servers which offer RFC 1730
350  *                 ;; compatibility MUST list "IMAP4" as the first
351  *                 ;; capability.
352  */
353
354 struct {
355         const gchar *name;
356         guint32 flag;
357 } capa_table[] = {
358         { "IMAP4", IMAPX_CAPABILITY_IMAP4 },
359         { "IMAP4REV1", IMAPX_CAPABILITY_IMAP4REV1 },
360         { "STATUS",  IMAPX_CAPABILITY_STATUS } ,
361         { "NAMESPACE", IMAPX_CAPABILITY_NAMESPACE },
362         { "UIDPLUS",  IMAPX_CAPABILITY_UIDPLUS },
363         { "LITERAL+", IMAPX_CAPABILITY_LITERALPLUS },
364         { "STARTTLS", IMAPX_CAPABILITY_STARTTLS },
365         { "IDLE", IMAPX_CAPABILITY_IDLE },
366         { "CONDSTORE", IMAPX_CAPABILITY_CONDSTORE },
367         { "QRESYNC", IMAPX_CAPABILITY_QRESYNC },
368         { "LIST-EXTENDED", IMAPX_CAPABILITY_LIST_EXTENDED },
369         { "LIST-STATUS", IMAPX_CAPABILITY_LIST_STATUS },
370 };
371
372 struct _capability_info *
373 imapx_parse_capability (CamelIMAPXStream *stream,
374                         GCancellable *cancellable,
375                         GError **error)
376 {
377         gint tok, i;
378         guint len;
379         guchar *token, *p, c;
380         gboolean free_token = FALSE;
381         struct _capability_info * cinfo;
382         GError *local_error = NULL;
383
384         cinfo = g_malloc0 (sizeof (*cinfo));
385         cinfo->auth_types = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
386
387         /* FIXME: handle auth types */
388         while ((tok = camel_imapx_stream_token (stream, &token, &len, cancellable, &local_error)) != '\n' &&
389                 local_error == NULL) {
390                 switch (tok) {
391                         case ']':
392                                 /* Put it back so that imapx_untagged() isn't unhappy */
393                                 camel_imapx_stream_ungettoken (stream, tok, token, len);
394                                 return cinfo;
395                         case 43:
396                                 token = (guchar *) g_strconcat ((gchar *)token, "+", NULL);
397                                 free_token = TRUE;
398                         case IMAPX_TOK_TOKEN:
399                         case IMAPX_TOK_STRING:
400                                 p = token;
401                                 while ((c = *p))
402                                         *p++ = toupper(c);
403                                 if (!strncmp ((gchar *) token, "AUTH=", 5)) {
404                                         g_hash_table_insert (cinfo->auth_types,
405                                                         g_strdup ((gchar *) token + 5),
406                                                         GINT_TO_POINTER (1));
407                                         break;
408                                 }
409                         case IMAPX_TOK_INT:
410                                 d(stream->tagprefix, " cap: '%s'\n", token);
411                                 for (i = 0; i < G_N_ELEMENTS (capa_table); i++)
412                                         if (!strcmp ((gchar *) token, capa_table[i].name))
413                                                 cinfo->capa |= capa_table[i].flag;
414                                 if (free_token) {
415                                         g_free (token);
416                                         token = NULL;
417                                 }
418                                 free_token = FALSE;
419                                 break;
420                         default:
421                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "capability: expecting name");
422                                 break;
423                 }
424         }
425
426         if (local_error != NULL) {
427                 g_propagate_error (error, local_error);
428                 imapx_free_capability (cinfo);
429                 cinfo = NULL;
430         }
431
432         return cinfo;
433 }
434
435 void imapx_free_capability (struct _capability_info *cinfo)
436 {
437         g_hash_table_destroy (cinfo->auth_types);
438         g_free (cinfo);
439 }
440
441 struct _CamelIMAPXNamespaceList *
442 imapx_parse_namespace_list (CamelIMAPXStream *stream,
443                             GCancellable *cancellable,
444                             GError **error)
445 {
446         CamelIMAPXStoreNamespace *namespaces[3], *node, *tail;
447         CamelIMAPXNamespaceList *nsl = NULL;
448         gint tok, i;
449         guint len;
450         guchar *token;
451         gint n = 0;
452
453         nsl = g_malloc0 (sizeof (CamelIMAPXNamespaceList));
454         nsl->personal = NULL;
455         nsl->shared = NULL;
456         nsl->other = NULL;
457
458         tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
459         do {
460                 namespaces[n] = NULL;
461                 tail = (CamelIMAPXStoreNamespace *) &namespaces[n];
462
463                 if (tok == '(') {
464                         tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
465
466                         while (tok == '(') {
467                                 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
468                                 if (tok != IMAPX_TOK_STRING) {
469                                         g_set_error (error, 1, CAMEL_IMAPX_ERROR, "namespace: expected a string path name");
470                                         goto exception;
471                                 }
472
473                                 node = g_new0 (CamelIMAPXStoreNamespace, 1);
474                                 node->next = NULL;
475                                 node->path = g_strdup ((gchar *) token);
476
477                                 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
478
479                                 if (tok == IMAPX_TOK_STRING) {
480                                         if (strlen ((gchar *) token) == 1) {
481                                                 node->sep = *token;
482                                         } else {
483                                                 if (*token)
484                                                         node->sep = node->path[strlen (node->path) - 1];
485                                                 else
486                                                         node->sep = '\0';
487                                         }
488                                 } else if (tok == IMAPX_TOK_TOKEN) {
489                                         /* will a NIL be possible here? */
490                                         node->sep = '\0';
491                                 } else {
492                                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "namespace: expected a string separtor");
493                                         g_free (node->path);
494                                         g_free (node);
495                                         goto exception;
496                                 }
497
498                                 tail->next = node;
499                                 tail = node;
500
501                                 if (*node->path && node->path[strlen (node->path) -1] == node->sep)
502                                         node->path[strlen (node->path) - 1] = '\0';
503
504                                 if (!g_ascii_strncasecmp (node->path, "INBOX", 5) &&
505                                                 (node->path[6] == '\0' || node->path[6] == node->sep ))
506                                         memcpy (node->path, "INBOX", 5);
507
508                                 /* TODO remove full_name later. not required */
509                                 node->full_name = g_strdup (node->path);
510
511                                 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
512                                 if (tok != ')') {
513                                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "namespace: expected a ')'");
514                                         goto exception;
515                                 }
516
517                                 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
518                         }
519
520                         if (tok != ')') {
521                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "namespace: expected a ')'");
522                                 goto exception;
523                         }
524
525                 } else if (tok == IMAPX_TOK_TOKEN && !strcmp ((gchar *) token, "NIL")) {
526                         namespaces[n] = NULL;
527                 } else {
528                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "namespace: expected either a '(' or NIL");
529                         goto exception;
530                 }
531
532                 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
533                 n++;
534         } while (n < 3);
535
536         nsl->personal = namespaces[0];
537         nsl->shared = namespaces[1];
538         nsl->other = namespaces[2];
539
540         return nsl;
541 exception:
542         g_free (nsl);
543         for (i = 0; i < 3; i++)
544                 imapx_namespace_clear (&namespaces[i]);
545
546         return NULL;
547 }
548
549 /*
550  * body            ::= "(" body_type_1part / body_type_mpart ")"
551  *
552  * body_extension  ::= nstring / number / "(" 1#body_extension ")"
553  *                     ;; Future expansion.  Client implementations
554  *                     ;; MUST accept body_extension fields.  Server
555  *                     ;; implementations MUST NOT generate
556  *                     ;; body_extension fields except as defined by
557  *                     ;; future standard or standards-track
558  *                     ;; revisions of this specification.
559  *
560  * body_ext_1part  ::= body_fld_md5[SPACE body_fld_dsp
561  *                 [SPACE body_fld_lang
562  *                 [SPACE 1#body_extension]]]
563  *                     ;; MUST NOT be returned on non-extensible
564  *                     ;; "BODY" fetch
565  *
566  * body_ext_mpart  ::= body_fld_param
567  *                 [SPACE body_fld_dsp SPACE body_fld_lang
568  *                 [SPACE 1#body_extension]]
569  *                     ;; MUST NOT be returned on non-extensible
570  *                     ;; "BODY" fetch
571  *
572  * body_fields     ::= body_fld_param SPACE body_fld_id SPACE
573  *                     body_fld_desc SPACE body_fld_enc SPACE
574  *                     body_fld_octets
575  *
576  * body_fld_desc   ::= nstring
577  *
578  * body_fld_dsp    ::= "(" string SPACE body_fld_param ")" / nil
579  *
580  * body_fld_enc    ::= (<"> ("7BIT" / "8BIT" / "BINARY" / "BASE64"/
581  *                     "QUOTED-PRINTABLE") <">) / string
582  *
583  * body_fld_id     ::= nstring
584  *
585  * body_fld_lang   ::= nstring / "(" 1#string ")"
586  *
587  * body_fld_lines  ::= number
588  *
589  * body_fld_md5    ::= nstring
590  *
591  * body_fld_octets ::= number
592  *
593  * body_fld_param  ::= "(" 1#(string SPACE string) ")" / nil
594  *
595  * body_type_1part ::= (body_type_basic / body_type_msg / body_type_text)
596  *                 [SPACE body_ext_1part]
597  *
598  * body_type_basic ::= media_basic SPACE body_fields
599  *                     ;; MESSAGE subtype MUST NOT be "RFC822"
600  *
601  * body_type_mpart ::= 1*body SPACE media_subtype
602  *                 [SPACE body_ext_mpart]
603  *
604  * body_type_msg   ::= media_message SPACE body_fields SPACE envelope
605  *                     SPACE body SPACE body_fld_lines
606  *
607  * body_type_text  ::= media_text SPACE body_fields SPACE body_fld_lines
608  *
609  * envelope        ::= "(" env_date SPACE env_subject SPACE env_from
610  *                     SPACE env_sender SPACE env_reply_to SPACE env_to
611  *                     SPACE env_cc SPACE env_bcc SPACE env_in_reply_to
612  *                     SPACE env_message_id ")"
613  *
614  * env_bcc         ::= "(" 1*address ")" / nil
615  *
616  * env_cc          ::= "(" 1*address ")" / nil
617  *
618  * env_date        ::= nstring
619  *
620  * env_from        ::= "(" 1*address ")" / nil
621  *
622  * env_in_reply_to ::= nstring
623  *
624  * env_message_id  ::= nstring
625  *
626  * env_reply_to    ::= "(" 1*address ")" / nil
627  *
628  * env_sender      ::= "(" 1*address ")" / nil
629  *
630  * env_subject     ::= nstring
631  *
632  * env_to          ::= "(" 1*address ")" / nil
633  *
634  * media_basic     ::= (<"> ("APPLICATION" / "AUDIO" / "IMAGE" /
635  *                     "MESSAGE" / "VIDEO") <">) / string)
636  *                     SPACE media_subtype
637  *                     ;; Defined in[MIME-IMT]
638  *
639  * media_message   ::= <"> "MESSAGE" <"> SPACE <"> "RFC822" <">
640  *                     ;; Defined in[MIME-IMT]
641  *
642  * media_subtype   ::= string
643  *                     ;; Defined in[MIME-IMT]
644  *
645  * media_text      ::= <"> "TEXT" <"> SPACE media_subtype
646  *                     ;; Defined in[MIME-IMT]
647  *
648  *  ( "type" "subtype"  body_fields [envelope body body_fld_lines]
649  *                              [body_fld_lines]
650  *
651  *  (("TEXT" "PLAIN" ("CHARSET"
652  *                      "US-ASCII") NIL NIL "7BIT" 1152 23)("TEXT" "PLAIN"
653  *                      ("CHARSET" "US-ASCII" "NAME" "cc.diff")
654  *                      "<960723163407.20117h@cac.washington.edu>"
655  *                      "Compiler diff" "BASE64" 4554 73) "MIXED"))
656  *
657  */
658
659 /*
660 struct _body_fields {
661         CamelContentType *ct;
662         gchar *msgid, *desc;
663         CamelTransferEncoding encoding;
664         guint32 size;
665         };*/
666
667 void
668 imapx_free_body (struct _CamelMessageContentInfo *cinfo)
669 {
670         struct _CamelMessageContentInfo *list, *next;
671
672         list = cinfo->childs;
673         while (list) {
674                 next = list->next;
675                 imapx_free_body (list);
676                 list = next;
677         }
678
679         if (cinfo->type)
680                 camel_content_type_unref (cinfo->type);
681         g_free (cinfo->id);
682         g_free (cinfo->description);
683         g_free (cinfo->encoding);
684         g_free (cinfo);
685 }
686
687 gboolean
688 imapx_parse_param_list (CamelIMAPXStream *is,
689                         struct _camel_header_param **plist,
690                         GCancellable *cancellable,
691                         GError **error)
692 {
693         gint tok;
694         guint len;
695         guchar *token;
696         gchar *param;
697
698         p(is->tagprefix, "body_fld_param\n");
699
700         /* body_fld_param  ::= "(" 1#(string SPACE string) ")" / nil */
701         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
702         if (tok == '(') {
703                 while (1) {
704                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
705                         if (tok == ')')
706                                 break;
707                         camel_imapx_stream_ungettoken (is, tok, token, len);
708
709                         camel_imapx_stream_astring (is, &token, cancellable, NULL);
710                         param = alloca (strlen ((gchar *) token) + 1);
711                         strcpy (param, (gchar *) token);
712                         camel_imapx_stream_astring (is, &token, cancellable, NULL);
713                         camel_header_set_param (plist, param, (gchar *) token);
714                 }
715         } /* else check nil?  no need */
716
717         return TRUE;
718 }
719
720 struct _CamelContentDisposition *
721 imapx_parse_ext_optional (CamelIMAPXStream *is,
722                           GCancellable *cancellable,
723                           GError **error)
724 {
725         gint tok;
726         guint len;
727         guchar *token;
728         struct _CamelContentDisposition *dinfo = NULL;
729         GError *local_error = NULL;
730
731         /* this parses both extension types, from the body_fld_dsp onwards */
732         /* although the grammars are different, they can be parsed the same way */
733
734         /* body_ext_1part  ::= body_fld_md5 [SPACE body_fld_dsp
735          * [SPACE body_fld_lang
736          * [SPACE 1#body_extension]]]
737          *    ;; MUST NOT be returned on non-extensible
738          *    ;; "BODY" fetch */
739
740         /* body_ext_mpart  ::= body_fld_param
741          * [SPACE body_fld_dsp SPACE body_fld_lang
742          * [SPACE 1#body_extension]]
743          *    ;; MUST NOT be returned on non-extensible
744          *    ;; "BODY" fetch */
745
746         /* body_fld_dsp    ::= "(" string SPACE body_fld_param ")" / nil */
747
748         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
749         switch (tok) {
750                 case '(':
751                         dinfo = g_malloc0 (sizeof (*dinfo));
752                         dinfo->refcount = 1;
753                         /* should be string */
754                         camel_imapx_stream_astring (is, &token, cancellable, NULL);
755
756                         dinfo->disposition = g_strdup ((gchar *) token);
757                         imapx_parse_param_list (is, &dinfo->params, cancellable, NULL);
758                 case IMAPX_TOK_TOKEN:
759                         d(is->tagprefix, "body_fld_dsp: NIL\n");
760                         break;
761                 default:
762                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "body_fld_disp: expecting nil or list");
763                         return NULL;
764         }
765
766         p(is->tagprefix, "body_fld_lang\n");
767
768         /* body_fld_lang   ::= nstring / "(" 1#string ")" */
769
770         /* we just drop the lang string/list, save it somewhere? */
771
772         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
773         switch (tok) {
774                 case '(':
775                         while (1) {
776                                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
777                                 if (tok == ')') {
778                                         break;
779                                 } else if (tok != IMAPX_TOK_STRING) {
780                                         g_clear_error (&local_error);
781                                         g_set_error (&local_error, CAMEL_IMAPX_ERROR, 1, "expecting string");
782                                         break;
783                                 }
784                         }
785                         break;
786                 case IMAPX_TOK_TOKEN:
787                         d(is->tagprefix, "body_fld_lang = nil\n");
788                         /* treat as 'nil' */
789                         break;
790                 case IMAPX_TOK_STRING:
791                         /* we have a string */
792                         break;
793                 case IMAPX_TOK_LITERAL:
794                         /* we have a literal string */
795                         camel_imapx_stream_set_literal (is, len);
796                         while (camel_imapx_stream_getl (is, &token, &len, cancellable, NULL) > 0) {
797                                 d(is->tagprefix, "Skip literal data '%.*s'\n", (gint)len, token);
798                         }
799                         break;
800
801         }
802
803         if (local_error != NULL) {
804                 g_propagate_error (error, local_error);
805                 if (dinfo)
806                         camel_content_disposition_unref (dinfo);
807                 dinfo = NULL;
808         }
809
810         return dinfo;
811 }
812
813 struct _CamelMessageContentInfo *
814 imapx_parse_body_fields (CamelIMAPXStream *is,
815                          GCancellable *cancellable,
816                          GError **error)
817 {
818         guchar *token;
819         gchar  *type;
820         struct _CamelMessageContentInfo *cinfo;
821         GError *local_error = NULL;
822
823         /* body_fields     ::= body_fld_param SPACE body_fld_id SPACE
824          * body_fld_desc SPACE body_fld_enc SPACE
825          * body_fld_octets */
826
827         p(is->tagprefix, "body_fields\n");
828
829         cinfo = g_malloc0 (sizeof (*cinfo));
830
831         /* this should be string not astring */
832         if (camel_imapx_stream_astring (is, &token, cancellable, error))
833                 goto error;
834         type = alloca (strlen ( (gchar *) token) + 1);
835         strcpy (type, (gchar *) token);
836         if (camel_imapx_stream_astring (is, &token, cancellable, error))
837                 goto error;
838         cinfo->type = camel_content_type_new (type, (gchar *) token);
839         if (!imapx_parse_param_list (is, &cinfo->type->params, cancellable, error))
840                 goto error;
841
842         /* body_fld_id     ::= nstring */
843         if (!camel_imapx_stream_nstring (is, &token, cancellable, error))
844                 goto error;
845         cinfo->id = g_strdup ((gchar *) token);
846
847         /* body_fld_desc   ::= nstring */
848         if (!camel_imapx_stream_nstring (is, &token, cancellable, error))
849                 goto error;
850         cinfo->description = g_strdup ((gchar *) token);
851
852         /* body_fld_enc    ::= (<"> ("7BIT" / "8BIT" / "BINARY" / "BASE64"/
853          * "QUOTED-PRINTABLE") <">) / string */
854         if (camel_imapx_stream_astring (is, &token, cancellable, error))
855                 goto error;
856         cinfo->encoding = g_strdup ((gchar *) token);
857
858         /* body_fld_octets ::= number */
859         cinfo->size = camel_imapx_stream_number (is, cancellable, &local_error);
860         if (local_error != NULL) {
861                 g_propagate_error (error, local_error);
862                 goto error;
863         }
864
865         return cinfo;
866 error:
867         imapx_free_body (cinfo);
868         return cinfo;
869 }
870
871 struct _camel_header_address *
872 imapx_parse_address_list (CamelIMAPXStream *is,
873                           GCancellable *cancellable,
874                           GError **error)
875 /* throws PARSE,IO exception */
876 {
877         gint tok;
878         guint len;
879         guchar *token, *host;
880         gchar *mbox;
881         struct _camel_header_address *list = NULL;
882         GError *local_error = NULL;
883
884         /* "(" 1*address ")" / nil */
885
886         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
887         if (tok == '(') {
888                 struct _camel_header_address *addr, *group = NULL;
889                 while (1) {
890                         /* address         ::= "(" addr_name SPACE addr_adl SPACE addr_mailbox
891                          * SPACE addr_host ")" */
892                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
893                         if (tok == ')')
894                                 break;
895                         if (tok != '(') {
896                                 g_clear_error (&local_error);
897                                 camel_header_address_list_clear (&list);
898                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "missing '(' for address");
899                                 return NULL;
900                         }
901
902                         addr = camel_header_address_new ();
903                         addr->type = CAMEL_HEADER_ADDRESS_NAME;
904                         tok = camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
905                         addr->name = g_strdup ((gchar *) token);
906                         /* we ignore the route, nobody uses it in the real world */
907                         tok = camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
908
909                         /* [RFC-822] group syntax is indicated by a special
910                          * form of address structure in which the host name
911                          * field is NIL.  If the mailbox name field is also
912                          * NIL, this is an end of group marker (semi-colon in
913                          * RFC 822 syntax).  If the mailbox name field is
914                          * non-NIL, this is a start of group marker, and the
915                          * mailbox name field holds the group name phrase. */
916
917                         tok = camel_imapx_stream_nstring (is,(guchar **) &mbox, cancellable, &local_error);
918                         mbox = g_strdup (mbox);
919                         tok = camel_imapx_stream_nstring (is, &host, cancellable, &local_error);
920                         if (host == NULL) {
921                                 if (mbox == NULL) {
922                                         group = NULL;
923                                 } else {
924                                         d(is->tagprefix, "adding group '%s'\n", mbox);
925                                         g_free (addr->name);
926                                         addr->name = mbox;
927                                         addr->type = CAMEL_HEADER_ADDRESS_GROUP;
928                                         camel_header_address_list_append (&list, addr);
929                                         group = addr;
930                                 }
931                         } else {
932                                 addr->v.addr = g_strdup_printf ("%s@%s", mbox? mbox :"", (const gchar *) host);
933                                 g_free (mbox);
934                                 d(is->tagprefix, "adding address '%s'\n", addr->v.addr);
935                                 if (group != NULL)
936                                         camel_header_address_add_member (group, addr);
937                                 else
938                                         camel_header_address_list_append (&list, addr);
939                         }
940                         do {
941                                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
942                         } while (tok != ')');
943                 }
944         } else {
945                 d(is->tagprefix, "empty, nil '%s'\n", token);
946         }
947
948         /* CHEN TODO handle exception at required places */
949         if (local_error != NULL)
950                 g_propagate_error (error, local_error);
951
952         return list;
953 }
954
955 struct _CamelMessageInfo *
956 imapx_parse_envelope (CamelIMAPXStream *is,
957                       GCancellable *cancellable,
958                       GError **error)
959 {
960         gint tok;
961         guint len;
962         guchar *token;
963         struct _camel_header_address *addr, *addr_from;
964         gchar *addrstr;
965         struct _CamelMessageInfoBase *minfo;
966         GError *local_error = NULL;
967
968         /* envelope        ::= "(" env_date SPACE env_subject SPACE env_from
969          * SPACE env_sender SPACE env_reply_to SPACE env_to
970          * SPACE env_cc SPACE env_bcc SPACE env_in_reply_to
971          * SPACE env_message_id ")" */
972
973         p(is->tagprefix, "envelope\n");
974
975         minfo = (CamelMessageInfoBase *) camel_message_info_new (NULL);
976
977         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
978         if (tok != '(') {
979                 g_clear_error (&local_error);
980                 camel_message_info_free (minfo);
981                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "envelope: expecting '('");
982                 return NULL;
983         }
984
985         /* env_date        ::= nstring */
986         camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
987         minfo->date_sent = camel_header_decode_date ((gchar *) token, NULL);
988
989         /* env_subject     ::= nstring */
990         tok = camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
991         minfo->subject = camel_pstring_strdup ((gchar *) token);
992
993         /* we merge from/sender into from, append should probably merge more smartly? */
994
995         /* env_from        ::= "(" 1*address ")" / nil */
996         addr_from = imapx_parse_address_list (is, cancellable, &local_error);
997
998         /* env_sender      ::= "(" 1*address ")" / nil */
999         addr = imapx_parse_address_list (is, cancellable, &local_error);
1000         if (addr_from) {
1001                 camel_header_address_list_clear (&addr);
1002 #if 0
1003                 if (addr)
1004                         camel_header_address_list_append_list (&addr_from, &addr);
1005 #endif
1006         } else {
1007                 if (addr)
1008                         addr_from = addr;
1009         }
1010
1011         if (addr_from) {
1012                 addrstr = camel_header_address_list_format (addr_from);
1013                 minfo->from = camel_pstring_strdup (addrstr);
1014                 g_free (addrstr);
1015                 camel_header_address_list_clear (&addr_from);
1016         }
1017
1018         /* we dont keep reply_to */
1019
1020         /* env_reply_to    ::= "(" 1*address ")" / nil */
1021         addr = imapx_parse_address_list (is, cancellable, &local_error);
1022         camel_header_address_list_clear (&addr);
1023
1024         /* env_to          ::= "(" 1*address ")" / nil */
1025         addr = imapx_parse_address_list (is, cancellable, &local_error);
1026         if (addr) {
1027                 addrstr = camel_header_address_list_format (addr);
1028                 minfo->to = camel_pstring_strdup (addrstr);
1029                 g_free (addrstr);
1030                 camel_header_address_list_clear (&addr);
1031         }
1032
1033         /* env_cc          ::= "(" 1*address ")" / nil */
1034         addr = imapx_parse_address_list (is, cancellable, &local_error);
1035         if (addr) {
1036                 addrstr = camel_header_address_list_format (addr);
1037                 minfo->cc = camel_pstring_strdup (addrstr);
1038                 g_free (addrstr);
1039                 camel_header_address_list_clear (&addr);
1040         }
1041
1042         /* we dont keep bcc either */
1043
1044         /* env_bcc         ::= "(" 1*address ")" / nil */
1045         addr = imapx_parse_address_list (is, cancellable, &local_error);
1046         camel_header_address_list_clear (&addr);
1047
1048         /* FIXME: need to put in-reply-to into references hash list */
1049
1050         /* env_in_reply_to ::= nstring */
1051         tok = camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1052
1053         /* FIXME: need to put message-id into message-id hash */
1054
1055         /* env_message_id  ::= nstring */
1056         tok = camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1057
1058         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1059         if (tok != ')') {
1060                 g_clear_error (&local_error);
1061                 camel_message_info_free (minfo);
1062                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting ')'");
1063                 return NULL;
1064         }
1065
1066         /* CHEN TODO handle exceptions better */
1067         if (local_error != NULL)
1068                 g_propagate_error (error, local_error);
1069
1070         return (CamelMessageInfo *) minfo;
1071 }
1072
1073 struct _CamelMessageContentInfo *
1074 imapx_parse_body (CamelIMAPXStream *is,
1075                   GCancellable *cancellable,
1076                   GError **error)
1077 {
1078         gint tok;
1079         guint len;
1080         guchar *token;
1081         struct _CamelMessageContentInfo * cinfo = NULL;
1082         struct _CamelMessageContentInfo *subinfo, *last;
1083         struct _CamelContentDisposition * dinfo = NULL;
1084         GError *local_error = NULL;
1085
1086         /* body            ::= "(" body_type_1part / body_type_mpart ")" */
1087
1088         p(is->tagprefix, "body\n");
1089
1090         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1091         if (tok != '(') {
1092                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "body: expecting '('");
1093                 return NULL;
1094         }
1095
1096         /* 1*body (optional for multiparts) */
1097         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1098         camel_imapx_stream_ungettoken (is, tok, token, len);
1099         if (tok == '(') {
1100                 /* body_type_mpart ::= 1*body SPACE media_subtype
1101                 [SPACE body_ext_mpart] */
1102
1103                 cinfo = g_malloc0 (sizeof (*cinfo));
1104                 last = (struct _CamelMessageContentInfo *) &cinfo->childs;
1105                 do {
1106                         subinfo = imapx_parse_body (is, cancellable, &local_error);
1107                         last->next = subinfo;
1108                         last = subinfo;
1109                         subinfo->parent = cinfo;
1110                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1111                         camel_imapx_stream_ungettoken (is, tok, token, len);
1112                 } while (tok == '(');
1113
1114                 d(is->tagprefix, "media_subtype\n");
1115
1116                 camel_imapx_stream_astring (is, &token, cancellable, &local_error);
1117                 cinfo->type = camel_content_type_new("multipart", (gchar *) token);
1118
1119                 /* body_ext_mpart  ::= body_fld_param
1120                  * [SPACE body_fld_dsp SPACE body_fld_lang
1121                  * [SPACE 1#body_extension]]
1122                  *    ;; MUST NOT be returned on non-extensible
1123                  *    ;; "BODY" fetch */
1124
1125                 d(is->tagprefix, "body_ext_mpart\n");
1126
1127                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1128                 camel_imapx_stream_ungettoken (is, tok, token, len);
1129                 if (tok == '(') {
1130                         imapx_parse_param_list (is, &cinfo->type->params, cancellable, &local_error);
1131
1132                         /* body_fld_dsp    ::= "(" string SPACE body_fld_param ")" / nil */
1133
1134                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1135                         camel_imapx_stream_ungettoken (is, tok, token, len);
1136                         if (tok == '(' || tok == IMAPX_TOK_TOKEN) {
1137                                 dinfo = imapx_parse_ext_optional (is, cancellable, &local_error);
1138                                 /* other extension fields?, soaked up below */
1139                         } else {
1140                                 camel_imapx_stream_ungettoken (is, tok, token, len);
1141                         }
1142                 }
1143         } else {
1144                 /* body_type_1part ::= (body_type_basic / body_type_msg / body_type_text)
1145                  * [SPACE body_ext_1part]
1146                  *
1147                  * body_type_basic ::= media_basic SPACE body_fields
1148                  * body_type_text  ::= media_text SPACE body_fields SPACE body_fld_lines
1149                  * body_type_msg   ::= media_message SPACE body_fields SPACE envelope
1150                  * SPACE body SPACE body_fld_lines */
1151
1152                 d(is->tagprefix, "Single part body\n");
1153
1154                 cinfo = imapx_parse_body_fields (is, cancellable, &local_error);
1155
1156                 d(is->tagprefix, "envelope?\n");
1157
1158                 /* do we have an envelope following */
1159                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1160                 camel_imapx_stream_ungettoken (is, tok, token, len);
1161                 if (tok == '(') {
1162                         struct _CamelMessageInfo * minfo = NULL;
1163
1164                         /* what do we do with the envelope?? */
1165                         minfo = imapx_parse_envelope (is, cancellable, &local_error);
1166                         /* what do we do with the message content info?? */
1167                         //((CamelMessageInfoBase *) minfo)->content = imapx_parse_body (is);
1168                         camel_message_info_free (minfo);
1169                         minfo = NULL;
1170                         d(is->tagprefix, "Scanned envelope - what do i do with it?\n");
1171                 }
1172
1173                 d(is->tagprefix, "fld_lines?\n");
1174
1175                 /* do we have fld_lines following? */
1176                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1177                 if (tok == IMAPX_TOK_INT) {
1178                         d(is->tagprefix, "field lines: %s\n", token);
1179                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1180                 }
1181                 camel_imapx_stream_ungettoken (is, tok, token, len);
1182
1183                 /* body_ext_1part  ::= body_fld_md5 [SPACE body_fld_dsp
1184                 [SPACE body_fld_lang
1185                 [SPACE 1#body_extension]]]
1186                  * ;; MUST NOT be returned on non - extensible
1187                  * ;; "BODY" fetch */
1188
1189                 d(is->tagprefix, "extension data?\n");
1190
1191                 if (tok != ')') {
1192                         camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1193
1194                         d(is->tagprefix, "md5: %s\n", token?(gchar *)token:"NIL");
1195
1196                         /* body_fld_dsp    ::= "(" string SPACE body_fld_param ")" / nil */
1197
1198                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1199                         camel_imapx_stream_ungettoken (is, tok, token, len);
1200                         if (tok == '(' || tok == IMAPX_TOK_TOKEN) {
1201                                 dinfo = imapx_parse_ext_optional (is, cancellable, &local_error);
1202                                 /* then other extension fields, soaked up below */
1203                         }
1204                 }
1205         }
1206
1207         /* soak up any other extension fields that may be present */
1208         /* there should only be simple tokens, no lists */
1209         do {
1210                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1211                 if (tok != ')') {
1212                         d(is->tagprefix, "Dropping extension data '%s'\n", token);
1213                 }
1214         } while (tok != ')');
1215
1216         /* CHEN TODO handle exceptions better */
1217         if (local_error != NULL) {
1218                 g_propagate_error (error, local_error);
1219                 if (cinfo)
1220                         imapx_free_body (cinfo);
1221                 if (dinfo)
1222                         camel_content_disposition_unref (dinfo);
1223                 return NULL;
1224         }
1225
1226         /* FIXME: do something with the disposition, currently we have no way to pass it out? */
1227         if (dinfo)
1228                 camel_content_disposition_unref (dinfo);
1229
1230         return cinfo;
1231 }
1232
1233 gchar *
1234 imapx_parse_section (CamelIMAPXStream *is,
1235                      GCancellable *cancellable,
1236                      GError **error)
1237 {
1238         gint tok;
1239         guint len;
1240         guchar *token;
1241         gchar * section = NULL;
1242
1243         /* currently we only return the part within the [section] specifier
1244          * any header fields are parsed, but dropped */
1245
1246         /*
1247          * section         ::= "[" [section_text /
1248          * (nz_number *["." nz_number] ["." (section_text / "MIME")])] "]"
1249          *
1250          * section_text    ::= "HEADER" / "HEADER.FIELDS" [".NOT"]
1251          * SPACE header_list / "TEXT"
1252          */
1253
1254         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1255         if (tok != '[') {
1256                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "section: expecting '['");
1257                 return NULL;
1258         }
1259
1260         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1261         if (tok == IMAPX_TOK_INT || tok == IMAPX_TOK_TOKEN)
1262                 section = g_strdup ((gchar *) token);
1263         else if (tok == ']') {
1264                 section = g_strdup("");
1265                 camel_imapx_stream_ungettoken (is, tok, token, len);
1266         } else {
1267                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "section: expecting token");
1268                 return NULL;
1269         }
1270
1271         /* header_list     ::= "(" 1#header_fld_name ")"
1272          * header_fld_name ::= astring */
1273
1274         /* we dont need the header specifiers */
1275         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1276         if (tok == '(') {
1277                 do {
1278                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1279                         if (tok == IMAPX_TOK_STRING || tok == IMAPX_TOK_TOKEN || tok == IMAPX_TOK_INT) {
1280                                 /* ?do something? */
1281                         } else if (tok != ')') {
1282                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "section: header fields: expecting string");
1283                                 g_free (section);
1284                                 return NULL;
1285                         }
1286                 } while (tok != ')');
1287                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1288         }
1289
1290         if (tok != ']') {
1291                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "section: expecting ']'");
1292                 g_free (section);
1293                 return NULL;
1294         }
1295
1296         return section;
1297 }
1298
1299 static guint64
1300 imapx_parse_modseq (CamelIMAPXStream *is,
1301                     GCancellable *cancellable,
1302                     GError **error)
1303 {
1304         guint64 ret;
1305         gint tok;
1306         guint len;
1307         guchar *token;
1308
1309         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1310         if (tok != '(') {
1311                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "fetch: expecting '('");
1312                 return 0;
1313         }
1314         ret = camel_imapx_stream_number (is, cancellable, error);
1315         if (ret == 0)
1316                 return 0;
1317
1318         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1319         if (tok != ')') {
1320                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "fetch: expecting '('");
1321                 return 0;
1322         }
1323         return ret;
1324 }
1325
1326 void
1327 imapx_free_fetch (struct _fetch_info *finfo)
1328 {
1329         if (finfo == NULL)
1330                 return;
1331
1332         if (finfo->body)
1333                 g_object_unref (finfo->body);
1334         if (finfo->text)
1335                 g_object_unref (finfo->text);
1336         if (finfo->header)
1337                 g_object_unref (finfo->header);
1338         if (finfo->minfo)
1339                 camel_message_info_free (finfo->minfo);
1340         if (finfo->cinfo)
1341                 imapx_free_body (finfo->cinfo);
1342         camel_flag_list_free (&finfo->user_flags);
1343         g_free (finfo->date);
1344         g_free (finfo->section);
1345         g_free (finfo->uid);
1346         g_free (finfo);
1347 }
1348
1349 /* debug, dump one out */
1350 void
1351 imapx_dump_fetch (struct _fetch_info *finfo)
1352 {
1353         CamelStream *sout;
1354         gchar *string;
1355         gint fd;
1356
1357         d('?', "Fetch info:\n");
1358         if (finfo == NULL) {
1359                 d('?', "Empty\n");
1360                 return;
1361         }
1362
1363         fd = dup (1);
1364         sout = camel_stream_fs_new_with_fd (fd);
1365         if (finfo->body) {
1366                 camel_stream_write_string (sout, "Body content:\n", NULL, NULL);
1367                 camel_stream_write_to_stream (finfo->body, sout, NULL, NULL);
1368                 g_seekable_seek (
1369                         G_SEEKABLE (finfo->body),
1370                         0, G_SEEK_SET, NULL, NULL);
1371         }
1372         if (finfo->text) {
1373                 camel_stream_write_string (sout, "Text content:\n", NULL, NULL);
1374                 camel_stream_write_to_stream (finfo->text, sout, NULL, NULL);
1375                 g_seekable_seek (
1376                         G_SEEKABLE (finfo->text),
1377                         0, G_SEEK_SET, NULL, NULL);
1378         }
1379         if (finfo->header) {
1380                 camel_stream_write_string (sout, "Header content:\n", NULL, NULL);
1381                 camel_stream_write_to_stream (finfo->header, sout, NULL, NULL);
1382                 g_seekable_seek (
1383                         G_SEEKABLE (finfo->header),
1384                         0, G_SEEK_SET, NULL, NULL);
1385         }
1386         if (finfo->minfo) {
1387                 camel_stream_write_string (sout, "Message Info:\n", NULL, NULL);
1388                 camel_message_info_dump (finfo->minfo);
1389         }
1390         if (finfo->cinfo) {
1391                 camel_stream_write_string (sout, "Content Info:\n", NULL, NULL);
1392                 //camel_content_info_dump (finfo->cinfo, 0);
1393         }
1394         if (finfo->got & FETCH_SIZE) {
1395                 string = g_strdup_printf ("Size: %d\n", (gint) finfo->size);
1396                 camel_stream_write_string (sout, string, NULL, NULL);
1397                 g_free (string);
1398         }
1399         if (finfo->got & FETCH_BODY) {
1400                 string = g_strdup_printf ("Offset: %d\n", (gint) finfo->offset);
1401                 camel_stream_write_string (sout, string, NULL, NULL);
1402                 g_free (string);
1403         }
1404         if (finfo->got & FETCH_FLAGS) {
1405                 string = g_strdup_printf ("Flags: %08x\n", (gint) finfo->flags);
1406                 camel_stream_write_string (sout, string, NULL, NULL);
1407                 g_free (string);
1408         }
1409         if (finfo->date) {
1410                 string = g_strdup_printf ("Data: '%s'\n", finfo->date);
1411                 camel_stream_write_string (sout, string, NULL, NULL);
1412                 g_free (string);
1413         }
1414         if (finfo->section) {
1415                 string = g_strdup_printf ("Section: '%s'\n", finfo->section);
1416                 camel_stream_write_string (sout, string, NULL, NULL);
1417                 g_free (string);
1418         }
1419         if (finfo->date) {
1420                 string = g_strdup_printf ("UID: '%s'\n", finfo->uid);
1421                 camel_stream_write_string (sout, string, NULL, NULL);
1422                 g_free (string);
1423         }
1424         g_object_unref (sout);
1425 }
1426
1427 struct _fetch_info *
1428 imapx_parse_fetch (CamelIMAPXStream *is,
1429                    GCancellable *cancellable,
1430                    GError **error)
1431 {
1432         gint tok;
1433         guint len;
1434         guchar *token, *p, c;
1435         struct _fetch_info *finfo;
1436
1437         finfo = g_malloc0 (sizeof (*finfo));
1438
1439         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1440         if (tok != '(') {
1441                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "fetch: expecting '('");
1442                 g_free (finfo);
1443                 return NULL;
1444         }
1445
1446         while ((tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL)) == IMAPX_TOK_TOKEN) {
1447
1448                 p = token;
1449                 while ((c=*p))
1450                         *p++ = toupper(c);
1451
1452                 switch (imapx_tokenise ((gchar *) token, len)) {
1453                         case IMAPX_ENVELOPE:
1454                                 finfo->minfo = imapx_parse_envelope (is, cancellable, NULL);
1455                                 finfo->got |= FETCH_MINFO;
1456                                 break;
1457                         case IMAPX_FLAGS:
1458                                 imapx_parse_flags (is, &finfo->flags, &finfo->user_flags, cancellable, NULL);
1459                                 finfo->got |= FETCH_FLAGS;
1460                                 break;
1461                         case IMAPX_INTERNALDATE:
1462                                 camel_imapx_stream_nstring (is, &token, cancellable, NULL);
1463                                 /* TODO: convert to camel format? */
1464                                 finfo->date = g_strdup ((gchar *) token);
1465                                 finfo->got |= FETCH_DATE;
1466                                 break;
1467                         case IMAPX_RFC822_HEADER:
1468                                 camel_imapx_stream_nstring_stream (is, &finfo->header, cancellable, NULL);
1469                                 finfo->got |= FETCH_HEADER;
1470                                 break;
1471                         case IMAPX_RFC822_TEXT:
1472                                 camel_imapx_stream_nstring_stream (is, &finfo->text, cancellable, NULL);
1473                                 finfo->got |= FETCH_TEXT;
1474                                 break;
1475                         case IMAPX_RFC822_SIZE:
1476                                 finfo->size = camel_imapx_stream_number (is, cancellable, NULL);
1477                                 finfo->got |= FETCH_SIZE;
1478                                 break;
1479                         case IMAPX_BODYSTRUCTURE:
1480                                 finfo->cinfo = imapx_parse_body (is, cancellable, NULL);
1481                                 finfo->got |= FETCH_CINFO;
1482                                 break;
1483                         case IMAPX_MODSEQ:
1484                                 finfo->modseq = imapx_parse_modseq (is, cancellable, NULL);
1485                                 finfo->got |= FETCH_MODSEQ;
1486                                 break;
1487                         case IMAPX_BODY:
1488                                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1489                                 camel_imapx_stream_ungettoken (is, tok, token, len);
1490                                 if (tok == '(') {
1491                                         finfo->cinfo = imapx_parse_body (is, cancellable, NULL);
1492                                         finfo->got |= FETCH_CINFO;
1493                                 } else if (tok == '[') {
1494                                         finfo->section = imapx_parse_section (is, cancellable, NULL);
1495                                         finfo->got |= FETCH_SECTION;
1496                                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1497                                         if (token[0] == '<') {
1498                                                 finfo->offset = strtoul ((gchar *) token + 1, NULL, 10);
1499                                         } else {
1500                                                 camel_imapx_stream_ungettoken (is, tok, token, len);
1501                                         }
1502                                         camel_imapx_stream_nstring_stream (is, &finfo->body, cancellable, NULL);
1503                                         finfo->got |= FETCH_BODY;
1504                                 } else {
1505                                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "unknown body response");
1506                                         imapx_free_fetch (finfo);
1507                                         return NULL;
1508                                 }
1509                                 break;
1510                         case IMAPX_UID:
1511                                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1512                                 if (tok != IMAPX_TOK_INT) {
1513                                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "uid not integer");
1514                                 }
1515
1516                                 finfo->uid = g_strdup ((gchar *) token);
1517                                 finfo->got |= FETCH_UID;
1518                                 break;
1519                         default:
1520                                 imapx_free_fetch (finfo);
1521                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "unknown body response");
1522                                 return NULL;
1523                 }
1524         }
1525
1526         if (tok != ')') {
1527                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "missing closing ')' on fetch response");
1528                 imapx_free_fetch (finfo);
1529                 return NULL;
1530         }
1531
1532         return finfo;
1533 }
1534
1535 struct _state_info *
1536 imapx_parse_status_info (CamelIMAPXStream *is,
1537                          GCancellable *cancellable,
1538                          GError **error)
1539 {
1540         struct _state_info *sinfo;
1541         gint tok;
1542         guint len;
1543         guchar *token;
1544
1545         sinfo = g_malloc0 (sizeof (*sinfo));
1546
1547         /* skip the folder name */
1548         if (camel_imapx_stream_astring (is, &token, cancellable, error)) {
1549                 g_free (sinfo);
1550                 return NULL;
1551         }
1552         sinfo->name = camel_utf7_utf8 ((gchar *) token);
1553
1554         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1555         if (tok != '(') {
1556                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "parse status info: expecting '('");
1557                 g_free (sinfo);
1558                 return NULL;
1559         }
1560
1561         while ((tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL)) == IMAPX_TOK_TOKEN) {
1562                 switch (imapx_tokenise ((gchar *) token, len)) {
1563                         case IMAPX_MESSAGES:
1564                                 sinfo->messages = camel_imapx_stream_number (is, cancellable, NULL);
1565                                 break;
1566                         case IMAPX_RECENT:
1567                                 sinfo->recent = camel_imapx_stream_number (is, cancellable, NULL);
1568                                 break;
1569                         case IMAPX_UIDNEXT:
1570                                 sinfo->uidnext = camel_imapx_stream_number (is, cancellable, NULL);
1571                                 break;
1572                         case IMAPX_UIDVALIDITY:
1573                                 sinfo->uidvalidity = camel_imapx_stream_number (is, cancellable, NULL);
1574                                 break;
1575                         case IMAPX_UNSEEN:
1576                                 sinfo->unseen = camel_imapx_stream_number (is, cancellable, NULL);
1577                                 break;
1578                         case IMAPX_HIGHESTMODSEQ:
1579                                 sinfo->highestmodseq = camel_imapx_stream_number (is, cancellable, NULL);
1580                                 break;
1581                         case IMAPX_NOMODSEQ:
1582                         break;
1583                         default:
1584                                 g_free (sinfo);
1585                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "unknown status response");
1586                                 return NULL;
1587                 }
1588         }
1589
1590         if (tok != ')') {
1591                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "missing closing ')' on status response");
1592                 g_free (sinfo);
1593                 return NULL;
1594         }
1595
1596         return sinfo;
1597 }
1598
1599 static void
1600 generate_uids_from_sequence (GPtrArray *uids,
1601                              guint32 begin_uid,
1602                              guint32 end_uid)
1603 {
1604         guint32 i;
1605
1606         for (i = begin_uid; i <= end_uid; i++)
1607                 g_ptr_array_add (uids, GUINT_TO_POINTER (i));
1608 }
1609
1610 GPtrArray *
1611 imapx_parse_uids (CamelIMAPXStream *is,
1612                   GCancellable *cancellable,
1613                   GError **error)
1614 {
1615         GPtrArray *uids = g_ptr_array_new ();
1616         guchar *token;
1617         gchar **splits;
1618         guint len, str_len;
1619         gint tok, i;
1620
1621         tok = camel_imapx_stream_token (is, &token, &len, cancellable, error);
1622         if (tok < 0)
1623                 return NULL;
1624
1625         splits = g_strsplit ((gchar *) token, ",", -1);
1626         str_len = g_strv_length (splits);
1627
1628         for (i = 0; i < str_len; i++)   {
1629                 if (g_strstr_len (splits [i], -1, ":")) {
1630                         gchar **seq = g_strsplit (splits [i], ":", -1);
1631                         guint32 uid1 = strtoul ((gchar *) seq[0], NULL, 10);
1632                         guint32 uid2 = strtoul ((gchar *) seq[1], NULL, 10);
1633
1634                         generate_uids_from_sequence (uids, uid1, uid2);
1635                         g_strfreev (seq);
1636                 } else {
1637                         guint32 uid = strtoul ((gchar *) splits[i], NULL, 10);
1638                         g_ptr_array_add (uids, GUINT_TO_POINTER (uid));
1639                 }
1640         }
1641
1642         g_strfreev (splits);
1643
1644         return uids;
1645 }
1646
1647 /* rfc 2060 section 7.1 Status Responses */
1648 /* shoudl this start after [ or before the [? token_unget anyone? */
1649 struct _status_info *
1650 imapx_parse_status (CamelIMAPXStream *is,
1651                     GCancellable *cancellable,
1652                     GError **error)
1653 {
1654         gint tok;
1655         guint len;
1656         guchar *token;
1657         struct _status_info *sinfo;
1658
1659         sinfo = g_malloc0 (sizeof (*sinfo));
1660
1661         camel_imapx_stream_atom (is, &token, &len, cancellable, NULL);
1662
1663         /*
1664          * resp_cond_auth  ::= ("OK" / "PREAUTH") SPACE resp_text
1665          * ;; Authentication condition
1666          *
1667          * resp_cond_bye   ::= "BYE" SPACE resp_text
1668          *
1669          * resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text
1670          * ;; Status condition
1671          */
1672
1673         sinfo->result = imapx_tokenise ((gchar *) token, len);
1674         switch (sinfo->result) {
1675                 case IMAPX_OK:
1676                 case IMAPX_NO:
1677                 case IMAPX_BAD:
1678                 case IMAPX_PREAUTH:
1679                 case IMAPX_BYE:
1680                         break;
1681                 default:
1682                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting OK/NO/BAD");
1683                         g_free (sinfo);
1684                         return NULL;
1685         }
1686
1687         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1688         if (tok == '[') {
1689                 camel_imapx_stream_atom (is, &token, &len, cancellable, NULL);
1690                 sinfo->condition = imapx_tokenise ((gchar *) token, len);
1691
1692                 /* parse any details */
1693                 switch (sinfo->condition) {
1694                         case IMAPX_READ_ONLY:
1695                         case IMAPX_READ_WRITE:
1696                         case IMAPX_ALERT:
1697                         case IMAPX_PARSE:
1698                         case IMAPX_TRYCREATE:
1699                         case IMAPX_CLOSED:
1700                                 break;
1701                         case IMAPX_APPENDUID:
1702                                 sinfo->u.appenduid.uidvalidity = camel_imapx_stream_number (is, cancellable, NULL);
1703                                 sinfo->u.appenduid.uid = camel_imapx_stream_number (is, cancellable, NULL);
1704                                 break;
1705                         case IMAPX_COPYUID:
1706                                 sinfo->u.copyuid.uidvalidity = camel_imapx_stream_number (is, cancellable, NULL);
1707                                 sinfo->u.copyuid.uids = imapx_parse_uids (is, cancellable, NULL);
1708                                 sinfo->u.copyuid.copied_uids = imapx_parse_uids (is, cancellable, NULL);
1709                                 break;
1710                         case IMAPX_NEWNAME:
1711                                 /* the rfc doesn't specify the bnf for this */
1712                                 camel_imapx_stream_astring (is, &token, cancellable, NULL);
1713                                 sinfo->u.newname.oldname = g_strdup ((gchar *) token);
1714                                 camel_imapx_stream_astring (is, &token, cancellable, NULL);
1715                                 sinfo->u.newname.newname = g_strdup ((gchar *) token);
1716                                 break;
1717                         case IMAPX_PERMANENTFLAGS:
1718                                 /* we only care about \* for permanent flags, not user flags */
1719                                 imapx_parse_flags (is, &sinfo->u.permanentflags, NULL, cancellable, NULL);
1720                                 break;
1721                         case IMAPX_UIDVALIDITY:
1722                                 sinfo->u.uidvalidity = camel_imapx_stream_number (is, cancellable, NULL);
1723                                 break;
1724                         case IMAPX_UIDNEXT:
1725                                 sinfo->u.uidnext = camel_imapx_stream_number (is, cancellable, NULL);
1726                                 break;
1727                         case IMAPX_UNSEEN:
1728                                 sinfo->u.unseen = camel_imapx_stream_number (is, cancellable, NULL);
1729                                 break;
1730                         case IMAPX_HIGHESTMODSEQ:
1731                                 sinfo->u.highestmodseq = camel_imapx_stream_number (is, cancellable, NULL);
1732                                 break;
1733                         case IMAPX_CAPABILITY:
1734                                 sinfo->u.cinfo = imapx_parse_capability (is, cancellable, NULL);
1735                                 break;
1736                         default:
1737                                 sinfo->condition = IMAPX_UNKNOWN;
1738                                 d(is->tagprefix, "Got unknown response code: %s: ignored\n", token);
1739                 }
1740
1741                 /* ignore anything we dont know about */
1742                 do {
1743                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1744                         if (tok == '\n' || tok < 0) {
1745                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "server response truncated");
1746                                 imapx_free_status (sinfo);
1747                                 return NULL;
1748                         }
1749                 } while (tok != ']');
1750         } else {
1751                 camel_imapx_stream_ungettoken (is, tok, token, len);
1752         }
1753
1754         /* and take the human readable response */
1755         camel_imapx_stream_text (is, (guchar **) &sinfo->text, cancellable, NULL);
1756
1757         return sinfo;
1758 }
1759
1760 struct _status_info *
1761 imapx_copy_status (struct _status_info *sinfo)
1762 {
1763         struct _status_info *out;
1764
1765         out = g_malloc (sizeof (*out));
1766         memcpy (out, sinfo, sizeof (*out));
1767         out->text = g_strdup (out->text);
1768         if (out->condition == IMAPX_NEWNAME) {
1769                 out->u.newname.oldname = g_strdup (out->u.newname.oldname);
1770                 out->u.newname.newname = g_strdup (out->u.newname.newname);
1771         }
1772
1773         return out;
1774 }
1775
1776 void
1777 imapx_free_status (struct _status_info *sinfo)
1778 {
1779         if (sinfo == NULL)
1780                 return;
1781
1782         switch (sinfo->condition) {
1783         case IMAPX_NEWNAME:
1784                 g_free (sinfo->u.newname.oldname);
1785                 g_free (sinfo->u.newname.newname);
1786                 break;
1787         case IMAPX_COPYUID:
1788                 g_ptr_array_free (sinfo->u.copyuid.uids, FALSE);
1789                 g_ptr_array_free (sinfo->u.copyuid.copied_uids, FALSE);
1790                 break;
1791         case IMAPX_CAPABILITY:
1792                 if (sinfo->u.cinfo)
1793                         imapx_free_capability (sinfo->u.cinfo);
1794                 break;
1795         default:
1796                 break;
1797         }
1798
1799         g_free (sinfo->text);
1800         g_free (sinfo);
1801 }
1802
1803 /* FIXME: use tokeniser? */
1804 /* FIXME: real flags */
1805 static struct {
1806         const gchar *name;
1807         guint32 flag;
1808 } list_flag_table[] = {
1809         { "\\NOINFERIORS", CAMEL_FOLDER_NOINFERIORS },
1810         { "\\NOSELECT", CAMEL_FOLDER_NOSELECT },
1811         { "\\MARKED", 1<< 16},
1812         { "\\UNMARKED", 1<< 17},
1813         { "\\SUBSCRIBED", CAMEL_FOLDER_SUBSCRIBED },
1814 };
1815
1816 struct _list_info *
1817 imapx_parse_list (CamelIMAPXStream *is,
1818                   GCancellable *cancellable,
1819                   GError **error)
1820 /* throws io, parse */
1821 {
1822         gint tok, i;
1823         guint len;
1824         guchar *token, *p, c;
1825         struct _list_info * linfo;
1826
1827         linfo = g_malloc0 (sizeof (*linfo));
1828
1829         /* mailbox_list    ::= "(" #("\Marked" / "\Noinferiors" /
1830          * "\Noselect" / "\Unmarked" / flag_extension) ")"
1831          * SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox */
1832
1833         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1834         if (tok != '(') {
1835                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "list: expecting '('");
1836                 g_free (linfo);
1837                 return NULL;
1838         }
1839
1840         while ((tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL)) != ')') {
1841                 if (tok == IMAPX_TOK_STRING || tok == IMAPX_TOK_TOKEN) {
1842                         p = token;
1843                         while ((c=*p))
1844                                 *p++ = toupper(c);
1845                         for (i = 0; i < G_N_ELEMENTS (list_flag_table); i++)
1846                                 if (!strcmp ((gchar *) token, list_flag_table[i].name))
1847                                         linfo->flags |= list_flag_table[i].flag;
1848                 } else {
1849                         imapx_free_list (linfo);
1850                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "list: execting flag or ')'");
1851                         return NULL;
1852                 }
1853         }
1854
1855         camel_imapx_stream_nstring (is, &token, cancellable, NULL);
1856         linfo->separator = token?*token : 0;
1857         camel_imapx_stream_astring (is, &token, cancellable, NULL);
1858         linfo->name = camel_utf7_utf8 ((gchar *) token);
1859
1860         return linfo;
1861 }
1862
1863 gchar *
1864 imapx_list_get_path (struct _list_info *li)
1865 {
1866         gchar *path, *p;
1867         gint c;
1868         const gchar *f;
1869
1870         if (li->separator != 0 && li->separator != '/') {
1871                 p = path = alloca (strlen (li->name) * 3 + 1);
1872                 f = li->name;
1873                 while ((c = *f++ & 0xff)) {
1874                         if (c == li->separator)
1875                                 *p++ = '/';
1876                         else if (c == '/' || c == '%')
1877                                 p += sprintf(p, "%%%02X", c);
1878                         else
1879                                 *p++ = c;
1880                 }
1881                 *p = 0;
1882         } else
1883                 path = li->name;
1884
1885         return camel_utf7_utf8 (path);
1886 }
1887
1888 void
1889 imapx_free_list (struct _list_info *linfo)
1890 {
1891         if (linfo) {
1892                 g_free (linfo->name);
1893                 g_free (linfo);
1894         }
1895 }
1896
1897 /* ********************************************************************** */
1898
1899 /*
1900  * From rfc2060
1901  *
1902  * ATOM_CHAR       ::= <any CHAR except atom_specials>
1903  *
1904  * atom_specials   ::= "(" / ")" / "{" / SPACE / CTL / list_wildcards /
1905  *                     quoted_specials
1906  *
1907  * CHAR            ::= <any 7 - bit US - ASCII character except NUL,
1908  *                      0x01 - 0x7f>
1909  *
1910  * CTL             ::= <any ASCII control character and DEL,
1911  *                         0x00 - 0x1f, 0x7f>
1912  *
1913  * SPACE           ::= <ASCII SP, space, 0x20>
1914  *
1915  * list_wildcards  ::= "%" / "*"
1916  *
1917  * quoted_specials ::= <"> / "\"
1918  *
1919  * string          ::= quoted / literal
1920  *
1921  * literal         ::= "{" number "}" CRLF *CHAR8
1922  *                     ;; Number represents the number of CHAR8 octets
1923  *
1924  * quoted          ::= <"> *QUOTED_CHAR <">
1925  *
1926  * QUOTED_CHAR     ::= <any TEXT_CHAR except quoted_specials> /
1927  *                     "\" quoted_specials
1928  *
1929  * TEXT_CHAR       ::= <any CHAR except CR and LF>
1930  */
1931
1932 /*
1933  * ATOM = 1
1934  * SIMPLE? = 2
1935  * NOTID? = 4
1936  *
1937  * QSPECIAL = 8
1938  */
1939
1940 guchar imapx_specials[256] = {
1941 /* 00 */0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 4, 0, 0,
1942 /* 10 */0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1943 /* 20 */4, 1, 0, 1, 1, 0, 1, 1, 0, 0, 2, 7, 1, 1, 1, 1,
1944 /* 30 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1945 /* 40 */7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1946 /* 50 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 0, 7, 1, 1,
1947 /* 60 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1948 /* 70 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0,
1949         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1950         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1951         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1952         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1953         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1954         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1955         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1956         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1957 };
1958
1959 #define list_wildcards "*%"
1960 #define quoted_specials "\\\""
1961 #define atom_specials "(){" list_wildcards quoted_specials /* + CTL */
1962
1963 /* special types for the tokeniser, come out as raw tokens */
1964 #define token_specials "\n*()[]+"
1965 #define notid_specials "\x20\r\n()[]+"
1966
1967 void imapx_utils_init (void)
1968 {
1969         gint i;
1970         guchar v;
1971
1972         for (i = 0; i < 128; i++) {
1973                 v = 0;
1974                 if (i >= 1 && i <= 0x7f) {
1975                         v |= IMAPX_TYPE_CHAR;
1976                         if (i != 0x0a && i != 0x0d) {
1977                                 v |= IMAPX_TYPE_TEXT_CHAR;
1978                                 if (i != '"' && i != '\\')
1979                                         v |= IMAPX_TYPE_QUOTED_CHAR;
1980                         }
1981                         if (i> 0x20 && i <0x7f && strchr (atom_specials, i) == NULL)
1982                                 v |= IMAPX_TYPE_ATOM_CHAR;
1983                         if (strchr (token_specials, i) != NULL)
1984                                 v |= IMAPX_TYPE_TOKEN_CHAR;
1985                         if (strchr (notid_specials, i) != NULL)
1986                                 v |= IMAPX_TYPE_NOTID_CHAR;
1987                 }
1988
1989                 imapx_specials[i] = v;
1990         }
1991         camel_imapx_set_debug_flags ();
1992 }
1993
1994 guchar imapx_is_mask (const gchar *p)
1995 {
1996         guchar v = 0xff;
1997
1998         while (*p) {
1999                 v &= imapx_specials[((guchar) * p) & 0xff];
2000                 p++;
2001         }
2002
2003         return v;
2004 }
2005
2006 gchar *
2007 imapx_path_to_physical (const gchar *prefix,
2008                         const gchar *vpath)
2009 {
2010         GString *out = g_string_new (prefix);
2011         const gchar *p = vpath;
2012         gchar c, *res;
2013
2014         g_string_append_c (out, '/');
2015         p = vpath;
2016         while ((c = *p++)) {
2017                 if (c == '/') {
2018                         g_string_append(out, "/" SUBFOLDER_DIR_NAME "/");
2019                         while (*p == '/')
2020                                 p++;
2021                 } else
2022                         g_string_append_c (out, c);
2023         }
2024
2025         res = out->str;
2026         g_string_free (out, FALSE);
2027
2028         return res;
2029 }
2030
2031 gchar *
2032 imapx_concat (CamelIMAPXStore *imapx_store,
2033               const gchar *prefix,
2034               const gchar *suffix)
2035 {
2036         gsize len;
2037
2038         len = strlen (prefix);
2039         if (len == 0 || prefix[len - 1] == imapx_store->dir_sep)
2040                 return g_strdup_printf ("%s%s", prefix, suffix);
2041         else
2042                 return g_strdup_printf ("%s%c%s", prefix, imapx_store->dir_sep, suffix);
2043 }
2044
2045 static void
2046 imapx_namespace_clear (CamelIMAPXStoreNamespace **ns)
2047 {
2048         CamelIMAPXStoreNamespace *node, *next;
2049
2050         node = *ns;
2051         while (node != NULL) {
2052                 next = node->next;
2053                 g_free (node->full_name);
2054                 g_free (node->path);
2055                 g_free (node);
2056                 node = next;
2057         }
2058
2059         *ns = NULL;
2060 }
2061
2062 void
2063 camel_imapx_namespace_list_clear (struct _CamelIMAPXNamespaceList *nsl)
2064 {
2065         if (!nsl)
2066                 return;
2067
2068         imapx_namespace_clear (&nsl->personal);
2069         imapx_namespace_clear (&nsl->shared);
2070         imapx_namespace_clear (&nsl->other);
2071
2072         g_free (nsl);
2073         nsl = NULL;
2074 }
2075
2076 static CamelIMAPXStoreNamespace *
2077 imapx_namespace_copy (const CamelIMAPXStoreNamespace *ns)
2078 {
2079         CamelIMAPXStoreNamespace *list, *node, *tail;
2080
2081         list = NULL;
2082         tail = (CamelIMAPXStoreNamespace *) &list;
2083
2084         while (ns != NULL) {
2085                 tail->next = node = g_malloc (sizeof (CamelIMAPXStoreNamespace));
2086                 node->path = g_strdup (ns->path);
2087                 node->sep = ns->sep;
2088                 ns = ns->next;
2089                 tail = node;
2090         }
2091
2092         tail->next = NULL;
2093
2094         return list;
2095 }
2096
2097 struct _CamelIMAPXNamespaceList *
2098 camel_imapx_namespace_list_copy (const struct _CamelIMAPXNamespaceList *nsl)
2099 {
2100         CamelIMAPXNamespaceList *new;
2101
2102         new = g_malloc (sizeof (CamelIMAPXNamespaceList));
2103         new->personal = imapx_namespace_copy (nsl->personal);
2104         new->other = imapx_namespace_copy (nsl->other);
2105         new->shared = imapx_namespace_copy (nsl->shared);
2106
2107         return new;
2108 }
2109
2110 gchar *
2111 imapx_get_temp_uid (void)
2112 {
2113         gchar *res;
2114
2115         static gint counter = 0;
2116         G_LOCK_DEFINE_STATIC (lock);
2117
2118         G_LOCK (lock);
2119         res = g_strdup_printf ("tempuid-%lx-%d",
2120                                (gulong) time (NULL),
2121                                counter++);
2122         G_UNLOCK (lock);
2123
2124         return res;
2125 }
2126
2127 void
2128 camel_imapx_destroy_job_queue_info (IMAPXJobQueueInfo *jinfo)
2129 {
2130         g_hash_table_destroy (jinfo->folders);
2131         g_free (jinfo);
2132 }