Extending test-client-custom-summary to try e_book_client_get_contacts_uids()
[platform/upstream/evolution-data-server.git] / camel / 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
322         settings = camel_service_ref_settings (service);
323
324         mobile_mode = camel_imapx_settings_get_mobile_mode (
325                 CAMEL_IMAPX_SETTINGS (settings));
326
327         g_object_unref (settings);
328
329         si = camel_store_summary_path ((CamelStoreSummary *) ((CamelIMAPXStore *) parent_store)->summary, full_name);
330         if (si) {
331                 guint32 unread, total;
332
333                 total = camel_folder_summary_count (folder->summary);
334                 unread = camel_folder_summary_get_unread_count (folder->summary);
335
336                 if (si->unread != unread || si->total != total) {
337
338                         if (!mobile_mode)
339                                 si->unread = unread;
340                         else
341                                 si->unread =  ((CamelIMAPXFolder *) folder)->unread_on_server;
342                         si->total = total;
343
344                         camel_store_summary_touch ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
345                         camel_store_summary_save ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
346                 }
347         }
348 }
349
350 /*
351  * capability_data ::= "CAPABILITY" SPACE [1#capability SPACE] "IMAP4rev1"
352  *                     [SPACE 1#capability]
353  *                 ;; IMAP4rev1 servers which offer RFC 1730
354  *                 ;; compatibility MUST list "IMAP4" as the first
355  *                 ;; capability.
356  */
357
358 struct {
359         const gchar *name;
360         guint32 flag;
361 } capa_table[] = { /* used to create capa_htable only */
362         { "IMAP4", IMAPX_CAPABILITY_IMAP4 },
363         { "IMAP4REV1", IMAPX_CAPABILITY_IMAP4REV1 },
364         { "STATUS",  IMAPX_CAPABILITY_STATUS } ,
365         { "NAMESPACE", IMAPX_CAPABILITY_NAMESPACE },
366         { "UIDPLUS",  IMAPX_CAPABILITY_UIDPLUS },
367         { "LITERAL+", IMAPX_CAPABILITY_LITERALPLUS },
368         { "STARTTLS", IMAPX_CAPABILITY_STARTTLS },
369         { "IDLE", IMAPX_CAPABILITY_IDLE },
370         { "CONDSTORE", IMAPX_CAPABILITY_CONDSTORE },
371         { "QRESYNC", IMAPX_CAPABILITY_QRESYNC },
372         { "LIST-EXTENDED", IMAPX_CAPABILITY_LIST_EXTENDED },
373         { "LIST-STATUS", IMAPX_CAPABILITY_LIST_STATUS },
374 };
375
376 static GMutex capa_htable_lock;         /* capabilities lookup table lock */
377 static GHashTable *capa_htable = NULL;  /* capabilities lookup table (extensible) */
378
379 static void
380 create_initial_capabilities_table (void)
381 {
382         gint i = 0;
383
384         /* call within g_init_once() only,
385          * or require table lock
386          */
387
388         /* TODO add imapx_utils_uninit()
389          *      to free hash table
390          */
391         capa_htable = g_hash_table_new_full (
392                 g_str_hash,
393                 g_str_equal,
394                 g_free,
395                 NULL);
396
397         for (i = 0; i < G_N_ELEMENTS (capa_table); i++) {
398                 g_hash_table_insert (
399                         capa_htable,
400                         g_strdup (capa_table[i].name),
401                         GUINT_TO_POINTER (capa_table[i].flag));
402         }
403 }
404
405 struct _capability_info *
406 imapx_parse_capability (CamelIMAPXStream *stream,
407                         GCancellable *cancellable,
408                         GError **error)
409 {
410         gint tok;
411         guint len;
412         guchar *token, *p, c;
413         gboolean free_token = FALSE;
414         struct _capability_info * cinfo;
415         GError *local_error = NULL;
416
417         cinfo = g_malloc0 (sizeof (*cinfo));
418         cinfo->auth_types = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, NULL);
419
420         /* FIXME: handle auth types */
421         while ((tok = camel_imapx_stream_token (stream, &token, &len, cancellable, &local_error)) != '\n' &&
422                 local_error == NULL) {
423                 switch (tok) {
424                         case ']':
425                                 /* Put it back so that imapx_untagged() isn't unhappy */
426                                 camel_imapx_stream_ungettoken (stream, tok, token, len);
427                                 return cinfo;
428                         case 43:
429                                 token = (guchar *) g_strconcat ((gchar *) token, "+", NULL);
430                                 free_token = TRUE;
431                         case IMAPX_TOK_TOKEN:
432                         case IMAPX_TOK_STRING:
433                                 p = token;
434                                 while ((c = *p))
435                                         *p++ = toupper(c);
436                                 if (!strncmp ((gchar *) token, "AUTH=", 5)) {
437                                         g_hash_table_insert (
438                                                 cinfo->auth_types,
439                                                 g_strdup ((gchar *) token + 5),
440                                                 GINT_TO_POINTER (1));
441                                         break;
442                                 }
443                         case IMAPX_TOK_INT:
444                                 d (stream->tagprefix, " cap: '%s'\n", token);
445                                 cinfo->capa |= imapx_lookup_capability ((gchar *) token);
446                                 if (free_token) {
447                                         g_free (token);
448                                         token = NULL;
449                                 }
450                                 free_token = FALSE;
451                                 break;
452                         default:
453                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "capability: expecting name");
454                                 break;
455                 }
456         }
457
458         if (local_error != NULL) {
459                 g_propagate_error (error, local_error);
460                 imapx_free_capability (cinfo);
461                 cinfo = NULL;
462         }
463
464         return cinfo;
465 }
466
467 void imapx_free_capability (struct _capability_info *cinfo)
468 {
469         g_hash_table_destroy (cinfo->auth_types);
470         g_free (cinfo);
471 }
472
473 guint32
474 imapx_register_capability (const gchar *capability)
475 {
476         guint32 capa_id = 0;
477         guint64 check_id = 0;
478         GList *vals = NULL;
479         GList *tmp_vals = NULL;
480
481         g_return_val_if_fail (capability != NULL, 0);
482
483         g_mutex_lock (&capa_htable_lock);
484
485         /* we rely on IMAP being the first flag, non-zero value
486          * (1 << 0), so we can use GPOINTER_TO_UINT (NULL) as
487          * invalid value
488          */
489         capa_id = GPOINTER_TO_UINT (
490                 g_hash_table_lookup (capa_htable, capability));
491         if (capa_id > 0)
492                 goto exit;
493
494         /* not yet there, find biggest flag so far */
495         vals = g_hash_table_get_values (capa_htable);
496         tmp_vals = vals;
497         while (tmp_vals != NULL) {
498                 guint32 tmp_id = GPOINTER_TO_UINT (tmp_vals->data);
499                 if (capa_id < tmp_id)
500                         capa_id = tmp_id;
501                 tmp_vals = g_list_next (tmp_vals);
502         }
503         g_list_free (vals);
504
505         /* shift-left biggest-so-far, sanity-check */
506         check_id = (capa_id << 1);
507         g_return_val_if_fail (check_id <= (guint64) G_MAXUINT32, 0);
508         capa_id = (guint32) check_id;
509
510         /* insert */
511         g_hash_table_insert (
512                 capa_htable,
513                 g_strdup (capability),
514                 GUINT_TO_POINTER (capa_id));
515
516  exit:
517         g_mutex_unlock (&capa_htable_lock);
518
519         return capa_id;
520 }
521
522 guint32
523 imapx_lookup_capability (const gchar *capability)
524 {
525         gpointer data;
526
527         g_return_val_if_fail (capability != NULL, 0);
528
529         g_mutex_lock (&capa_htable_lock);
530
531         data = g_hash_table_lookup (capa_htable, capability);
532
533         g_mutex_unlock (&capa_htable_lock);
534
535         return GPOINTER_TO_UINT (data);
536 }
537
538 struct _CamelIMAPXNamespaceList *
539 imapx_parse_namespace_list (CamelIMAPXStream *stream,
540                             GCancellable *cancellable,
541                             GError **error)
542 {
543         CamelIMAPXStoreNamespace *namespaces[3], *node, *tail;
544         CamelIMAPXNamespaceList *nsl = NULL;
545         gint tok, i;
546         guint len;
547         guchar *token;
548         gint n = 0;
549
550         nsl = g_malloc0 (sizeof (CamelIMAPXNamespaceList));
551         nsl->personal = NULL;
552         nsl->shared = NULL;
553         nsl->other = NULL;
554
555         tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
556         do {
557                 namespaces[n] = NULL;
558                 tail = (CamelIMAPXStoreNamespace *) &namespaces[n];
559
560                 if (tok == '(') {
561                         tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
562
563                         while (tok == '(') {
564                                 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
565                                 if (tok != IMAPX_TOK_STRING) {
566                                         g_set_error (error, 1, CAMEL_IMAPX_ERROR, "namespace: expected a string path name");
567                                         goto exception;
568                                 }
569
570                                 node = g_new0 (CamelIMAPXStoreNamespace, 1);
571                                 node->next = NULL;
572                                 node->path = g_strdup ((gchar *) token);
573
574                                 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
575
576                                 if (tok == IMAPX_TOK_STRING) {
577                                         if (strlen ((gchar *) token) == 1) {
578                                                 node->sep = *token;
579                                         } else {
580                                                 if (*token)
581                                                         node->sep = node->path[strlen (node->path) - 1];
582                                                 else
583                                                         node->sep = '\0';
584                                         }
585                                 } else if (tok == IMAPX_TOK_TOKEN) {
586                                         /* will a NIL be possible here? */
587                                         node->sep = '\0';
588                                 } else {
589                                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "namespace: expected a string separtor");
590                                         g_free (node->path);
591                                         g_free (node);
592                                         goto exception;
593                                 }
594
595                                 tail->next = node;
596                                 tail = node;
597
598                                 if (*node->path && node->path[strlen (node->path) -1] == node->sep)
599                                         node->path[strlen (node->path) - 1] = '\0';
600
601                                 if (!g_ascii_strncasecmp (node->path, "INBOX", 5) &&
602                                                 (node->path[6] == '\0' || node->path[6] == node->sep ))
603                                         memcpy (node->path, "INBOX", 5);
604
605                                 /* TODO remove full_name later. not required */
606                                 node->full_name = g_strdup (node->path);
607
608                                 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
609                                 if (tok != ')') {
610                                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "namespace: expected a ')'");
611                                         goto exception;
612                                 }
613
614                                 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
615                         }
616
617                         if (tok != ')') {
618                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "namespace: expected a ')'");
619                                 goto exception;
620                         }
621
622                 } else if (tok == IMAPX_TOK_TOKEN && !strcmp ((gchar *) token, "NIL")) {
623                         namespaces[n] = NULL;
624                 } else {
625                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "namespace: expected either a '(' or NIL");
626                         goto exception;
627                 }
628
629                 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
630                 n++;
631         } while (n < 3);
632
633         nsl->personal = namespaces[0];
634         nsl->shared = namespaces[1];
635         nsl->other = namespaces[2];
636
637         return nsl;
638 exception:
639         g_free (nsl);
640         for (i = 0; i < 3; i++)
641                 imapx_namespace_clear (&namespaces[i]);
642
643         return NULL;
644 }
645
646 /*
647  * body            ::= "(" body_type_1part / body_type_mpart ")"
648  *
649  * body_extension  ::= nstring / number / "(" 1#body_extension ")"
650  *                     ;; Future expansion.  Client implementations
651  *                     ;; MUST accept body_extension fields.  Server
652  *                     ;; implementations MUST NOT generate
653  *                     ;; body_extension fields except as defined by
654  *                     ;; future standard or standards-track
655  *                     ;; revisions of this specification.
656  *
657  * body_ext_1part  ::= body_fld_md5[SPACE body_fld_dsp
658  *                 [SPACE body_fld_lang
659  *                 [SPACE 1#body_extension]]]
660  *                     ;; MUST NOT be returned on non-extensible
661  *                     ;; "BODY" fetch
662  *
663  * body_ext_mpart  ::= body_fld_param
664  *                 [SPACE body_fld_dsp SPACE body_fld_lang
665  *                 [SPACE 1#body_extension]]
666  *                     ;; MUST NOT be returned on non-extensible
667  *                     ;; "BODY" fetch
668  *
669  * body_fields     ::= body_fld_param SPACE body_fld_id SPACE
670  *                     body_fld_desc SPACE body_fld_enc SPACE
671  *                     body_fld_octets
672  *
673  * body_fld_desc   ::= nstring
674  *
675  * body_fld_dsp    ::= "(" string SPACE body_fld_param ")" / nil
676  *
677  * body_fld_enc    ::= (<"> ("7BIT" / "8BIT" / "BINARY" / "BASE64"/
678  *                     "QUOTED-PRINTABLE") <">) / string
679  *
680  * body_fld_id     ::= nstring
681  *
682  * body_fld_lang   ::= nstring / "(" 1#string ")"
683  *
684  * body_fld_lines  ::= number
685  *
686  * body_fld_md5    ::= nstring
687  *
688  * body_fld_octets ::= number
689  *
690  * body_fld_param  ::= "(" 1#(string SPACE string) ")" / nil
691  *
692  * body_type_1part ::= (body_type_basic / body_type_msg / body_type_text)
693  *                 [SPACE body_ext_1part]
694  *
695  * body_type_basic ::= media_basic SPACE body_fields
696  *                     ;; MESSAGE subtype MUST NOT be "RFC822"
697  *
698  * body_type_mpart ::= 1*body SPACE media_subtype
699  *                 [SPACE body_ext_mpart]
700  *
701  * body_type_msg   ::= media_message SPACE body_fields SPACE envelope
702  *                     SPACE body SPACE body_fld_lines
703  *
704  * body_type_text  ::= media_text SPACE body_fields SPACE body_fld_lines
705  *
706  * envelope        ::= "(" env_date SPACE env_subject SPACE env_from
707  *                     SPACE env_sender SPACE env_reply_to SPACE env_to
708  *                     SPACE env_cc SPACE env_bcc SPACE env_in_reply_to
709  *                     SPACE env_message_id ")"
710  *
711  * env_bcc         ::= "(" 1*address ")" / nil
712  *
713  * env_cc          ::= "(" 1*address ")" / nil
714  *
715  * env_date        ::= nstring
716  *
717  * env_from        ::= "(" 1*address ")" / nil
718  *
719  * env_in_reply_to ::= nstring
720  *
721  * env_message_id  ::= nstring
722  *
723  * env_reply_to    ::= "(" 1*address ")" / nil
724  *
725  * env_sender      ::= "(" 1*address ")" / nil
726  *
727  * env_subject     ::= nstring
728  *
729  * env_to          ::= "(" 1*address ")" / nil
730  *
731  * media_basic     ::= (<"> ("APPLICATION" / "AUDIO" / "IMAGE" /
732  *                     "MESSAGE" / "VIDEO") <">) / string)
733  *                     SPACE media_subtype
734  *                     ;; Defined in[MIME-IMT]
735  *
736  * media_message   ::= <"> "MESSAGE" <"> SPACE <"> "RFC822" <">
737  *                     ;; Defined in[MIME-IMT]
738  *
739  * media_subtype   ::= string
740  *                     ;; Defined in[MIME-IMT]
741  *
742  * media_text      ::= <"> "TEXT" <"> SPACE media_subtype
743  *                     ;; Defined in[MIME-IMT]
744  *
745  *  ( "type" "subtype"  body_fields [envelope body body_fld_lines]
746  *                              [body_fld_lines]
747  *
748  *  (("TEXT" "PLAIN" ("CHARSET"
749  *                      "US-ASCII") NIL NIL "7BIT" 1152 23)("TEXT" "PLAIN"
750  *                      ("CHARSET" "US-ASCII" "NAME" "cc.diff")
751  *                      "<960723163407.20117h@cac.washington.edu>"
752  *                      "Compiler diff" "BASE64" 4554 73) "MIXED"))
753  *
754  */
755
756 /*
757 struct _body_fields {
758         CamelContentType *ct;
759         gchar *msgid, *desc;
760         CamelTransferEncoding encoding;
761         guint32 size;
762         };*/
763
764 void
765 imapx_free_body (struct _CamelMessageContentInfo *cinfo)
766 {
767         struct _CamelMessageContentInfo *list, *next;
768
769         list = cinfo->childs;
770         while (list) {
771                 next = list->next;
772                 imapx_free_body (list);
773                 list = next;
774         }
775
776         if (cinfo->type)
777                 camel_content_type_unref (cinfo->type);
778         g_free (cinfo->id);
779         g_free (cinfo->description);
780         g_free (cinfo->encoding);
781         g_free (cinfo);
782 }
783
784 gboolean
785 imapx_parse_param_list (CamelIMAPXStream *is,
786                         struct _camel_header_param **plist,
787                         GCancellable *cancellable,
788                         GError **error)
789 {
790         gint tok;
791         guint len;
792         guchar *token;
793         gchar *param;
794
795         p (is->tagprefix, "body_fld_param\n");
796
797         /* body_fld_param  ::= "(" 1#(string SPACE string) ")" / nil */
798         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
799         if (tok == '(') {
800                 while (1) {
801                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
802                         if (tok == ')')
803                                 break;
804                         camel_imapx_stream_ungettoken (is, tok, token, len);
805
806                         camel_imapx_stream_astring (is, &token, cancellable, NULL);
807                         param = alloca (strlen ((gchar *) token) + 1);
808                         strcpy (param, (gchar *) token);
809                         camel_imapx_stream_astring (is, &token, cancellable, NULL);
810                         camel_header_set_param (plist, param, (gchar *) token);
811                 }
812         } /* else check nil?  no need */
813
814         return TRUE;
815 }
816
817 struct _CamelContentDisposition *
818 imapx_parse_ext_optional (CamelIMAPXStream *is,
819                           GCancellable *cancellable,
820                           GError **error)
821 {
822         gint tok;
823         guint len;
824         guchar *token;
825         struct _CamelContentDisposition *dinfo = NULL;
826         GError *local_error = NULL;
827
828         /* this parses both extension types, from the body_fld_dsp onwards */
829         /* although the grammars are different, they can be parsed the same way */
830
831         /* body_ext_1part  ::= body_fld_md5 [SPACE body_fld_dsp
832          * [SPACE body_fld_lang
833          * [SPACE 1#body_extension]]]
834          *    ;; MUST NOT be returned on non-extensible
835          *    ;; "BODY" fetch */
836
837         /* body_ext_mpart  ::= body_fld_param
838          * [SPACE body_fld_dsp SPACE body_fld_lang
839          * [SPACE 1#body_extension]]
840          *    ;; MUST NOT be returned on non-extensible
841          *    ;; "BODY" fetch */
842
843         /* body_fld_dsp    ::= "(" string SPACE body_fld_param ")" / nil */
844
845         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
846         switch (tok) {
847                 case '(':
848                         dinfo = g_malloc0 (sizeof (*dinfo));
849                         dinfo->refcount = 1;
850                         /* should be string */
851                         camel_imapx_stream_astring (is, &token, cancellable, NULL);
852
853                         dinfo->disposition = g_strdup ((gchar *) token);
854                         imapx_parse_param_list (is, &dinfo->params, cancellable, NULL);
855                 case IMAPX_TOK_TOKEN:
856                         d (is->tagprefix, "body_fld_dsp: NIL\n");
857                         break;
858                 default:
859                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "body_fld_disp: expecting nil or list");
860                         return NULL;
861         }
862
863         p (is->tagprefix, "body_fld_lang\n");
864
865         /* body_fld_lang   ::= nstring / "(" 1#string ")" */
866
867         /* we just drop the lang string/list, save it somewhere? */
868
869         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
870         switch (tok) {
871                 case '(':
872                         while (1) {
873                                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
874                                 if (tok == ')') {
875                                         break;
876                                 } else if (tok != IMAPX_TOK_STRING) {
877                                         g_clear_error (&local_error);
878                                         g_set_error (&local_error, CAMEL_IMAPX_ERROR, 1, "expecting string");
879                                         break;
880                                 }
881                         }
882                         break;
883                 case IMAPX_TOK_TOKEN:
884                         d (is->tagprefix, "body_fld_lang = nil\n");
885                         /* treat as 'nil' */
886                         break;
887                 case IMAPX_TOK_STRING:
888                         /* we have a string */
889                         break;
890                 case IMAPX_TOK_LITERAL:
891                         /* we have a literal string */
892                         camel_imapx_stream_set_literal (is, len);
893                         while (camel_imapx_stream_getl (is, &token, &len, cancellable, NULL) > 0) {
894                                 d (is->tagprefix, "Skip literal data '%.*s'\n", (gint) len, token);
895                         }
896                         break;
897
898         }
899
900         if (local_error != NULL) {
901                 g_propagate_error (error, local_error);
902                 if (dinfo)
903                         camel_content_disposition_unref (dinfo);
904                 dinfo = NULL;
905         }
906
907         return dinfo;
908 }
909
910 struct _CamelMessageContentInfo *
911 imapx_parse_body_fields (CamelIMAPXStream *is,
912                          GCancellable *cancellable,
913                          GError **error)
914 {
915         guchar *token;
916         gchar  *type;
917         struct _CamelMessageContentInfo *cinfo;
918         GError *local_error = NULL;
919
920         /* body_fields     ::= body_fld_param SPACE body_fld_id SPACE
921          * body_fld_desc SPACE body_fld_enc SPACE
922          * body_fld_octets */
923
924         p (is->tagprefix, "body_fields\n");
925
926         cinfo = g_malloc0 (sizeof (*cinfo));
927
928         /* this should be string not astring */
929         if (camel_imapx_stream_astring (is, &token, cancellable, error))
930                 goto error;
931         type = alloca (strlen ( (gchar *) token) + 1);
932         strcpy (type, (gchar *) token);
933         if (camel_imapx_stream_astring (is, &token, cancellable, error))
934                 goto error;
935         cinfo->type = camel_content_type_new (type, (gchar *) token);
936         if (!imapx_parse_param_list (is, &cinfo->type->params, cancellable, error))
937                 goto error;
938
939         /* body_fld_id     ::= nstring */
940         if (!camel_imapx_stream_nstring (is, &token, cancellable, error))
941                 goto error;
942         cinfo->id = g_strdup ((gchar *) token);
943
944         /* body_fld_desc   ::= nstring */
945         if (!camel_imapx_stream_nstring (is, &token, cancellable, error))
946                 goto error;
947         cinfo->description = g_strdup ((gchar *) token);
948
949         /* body_fld_enc    ::= (<"> ("7BIT" / "8BIT" / "BINARY" / "BASE64"/
950          * "QUOTED-PRINTABLE") <">) / string */
951         if (camel_imapx_stream_astring (is, &token, cancellable, error))
952                 goto error;
953         cinfo->encoding = g_strdup ((gchar *) token);
954
955         /* body_fld_octets ::= number */
956         cinfo->size = camel_imapx_stream_number (is, cancellable, &local_error);
957         if (local_error != NULL) {
958                 g_propagate_error (error, local_error);
959                 goto error;
960         }
961
962         return cinfo;
963 error:
964         imapx_free_body (cinfo);
965         return cinfo;
966 }
967
968 struct _camel_header_address *
969 imapx_parse_address_list (CamelIMAPXStream *is,
970                           GCancellable *cancellable,
971                           GError **error)
972 /* throws PARSE,IO exception */
973 {
974         gint tok;
975         guint len;
976         guchar *token, *host;
977         gchar *mbox;
978         struct _camel_header_address *list = NULL;
979         GError *local_error = NULL;
980
981         /* "(" 1*address ")" / nil */
982
983         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
984         if (tok == '(') {
985                 struct _camel_header_address *addr, *group = NULL;
986                 while (1) {
987                         /* address         ::= "(" addr_name SPACE addr_adl SPACE addr_mailbox
988                          * SPACE addr_host ")" */
989                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
990                         if (tok == ')')
991                                 break;
992                         if (tok != '(') {
993                                 g_clear_error (&local_error);
994                                 camel_header_address_list_clear (&list);
995                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "missing '(' for address");
996                                 return NULL;
997                         }
998
999                         addr = camel_header_address_new ();
1000                         addr->type = CAMEL_HEADER_ADDRESS_NAME;
1001                         tok = camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1002                         addr->name = g_strdup ((gchar *) token);
1003                         /* we ignore the route, nobody uses it in the real world */
1004                         tok = camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1005
1006                         /* [RFC-822] group syntax is indicated by a special
1007                          * form of address structure in which the host name
1008                          * field is NIL.  If the mailbox name field is also
1009                          * NIL, this is an end of group marker (semi-colon in
1010                          * RFC 822 syntax).  If the mailbox name field is
1011                          * non-NIL, this is a start of group marker, and the
1012                          * mailbox name field holds the group name phrase. */
1013
1014                         tok = camel_imapx_stream_nstring (is,(guchar **) &mbox, cancellable, &local_error);
1015                         mbox = g_strdup (mbox);
1016                         tok = camel_imapx_stream_nstring (is, &host, cancellable, &local_error);
1017                         if (host == NULL) {
1018                                 if (mbox == NULL) {
1019                                         group = NULL;
1020                                 } else {
1021                                         d (is->tagprefix, "adding group '%s'\n", mbox);
1022                                         g_free (addr->name);
1023                                         addr->name = mbox;
1024                                         addr->type = CAMEL_HEADER_ADDRESS_GROUP;
1025                                         camel_header_address_list_append (&list, addr);
1026                                         group = addr;
1027                                 }
1028                         } else {
1029                                 addr->v.addr = g_strdup_printf ("%s@%s", mbox? mbox :"", (const gchar *) host);
1030                                 g_free (mbox);
1031                                 d (is->tagprefix, "adding address '%s'\n", addr->v.addr);
1032                                 if (group != NULL)
1033                                         camel_header_address_add_member (group, addr);
1034                                 else
1035                                         camel_header_address_list_append (&list, addr);
1036                         }
1037                         do {
1038                                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1039                         } while (tok != ')');
1040                 }
1041         } else {
1042                 d (is->tagprefix, "empty, nil '%s'\n", token);
1043         }
1044
1045         /* CHEN TODO handle exception at required places */
1046         if (local_error != NULL)
1047                 g_propagate_error (error, local_error);
1048
1049         return list;
1050 }
1051
1052 struct _CamelMessageInfo *
1053 imapx_parse_envelope (CamelIMAPXStream *is,
1054                       GCancellable *cancellable,
1055                       GError **error)
1056 {
1057         gint tok;
1058         guint len;
1059         guchar *token;
1060         struct _camel_header_address *addr, *addr_from;
1061         gchar *addrstr;
1062         struct _CamelMessageInfoBase *minfo;
1063         GError *local_error = NULL;
1064
1065         /* envelope        ::= "(" env_date SPACE env_subject SPACE env_from
1066          * SPACE env_sender SPACE env_reply_to SPACE env_to
1067          * SPACE env_cc SPACE env_bcc SPACE env_in_reply_to
1068          * SPACE env_message_id ")" */
1069
1070         p (is->tagprefix, "envelope\n");
1071
1072         minfo = (CamelMessageInfoBase *) camel_message_info_new (NULL);
1073
1074         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1075         if (tok != '(') {
1076                 g_clear_error (&local_error);
1077                 camel_message_info_free (minfo);
1078                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "envelope: expecting '('");
1079                 return NULL;
1080         }
1081
1082         /* env_date        ::= nstring */
1083         camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1084         minfo->date_sent = camel_header_decode_date ((gchar *) token, NULL);
1085
1086         /* env_subject     ::= nstring */
1087         tok = camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1088         minfo->subject = camel_pstring_strdup ((gchar *) token);
1089
1090         /* we merge from/sender into from, append should probably merge more smartly? */
1091
1092         /* env_from        ::= "(" 1*address ")" / nil */
1093         addr_from = imapx_parse_address_list (is, cancellable, &local_error);
1094
1095         /* env_sender      ::= "(" 1*address ")" / nil */
1096         addr = imapx_parse_address_list (is, cancellable, &local_error);
1097         if (addr_from) {
1098                 camel_header_address_list_clear (&addr);
1099 #if 0
1100                 if (addr)
1101                         camel_header_address_list_append_list (&addr_from, &addr);
1102 #endif
1103         } else {
1104                 if (addr)
1105                         addr_from = addr;
1106         }
1107
1108         if (addr_from) {
1109                 addrstr = camel_header_address_list_format (addr_from);
1110                 minfo->from = camel_pstring_strdup (addrstr);
1111                 g_free (addrstr);
1112                 camel_header_address_list_clear (&addr_from);
1113         }
1114
1115         /* we dont keep reply_to */
1116
1117         /* env_reply_to    ::= "(" 1*address ")" / nil */
1118         addr = imapx_parse_address_list (is, cancellable, &local_error);
1119         camel_header_address_list_clear (&addr);
1120
1121         /* env_to          ::= "(" 1*address ")" / nil */
1122         addr = imapx_parse_address_list (is, cancellable, &local_error);
1123         if (addr) {
1124                 addrstr = camel_header_address_list_format (addr);
1125                 minfo->to = camel_pstring_strdup (addrstr);
1126                 g_free (addrstr);
1127                 camel_header_address_list_clear (&addr);
1128         }
1129
1130         /* env_cc          ::= "(" 1*address ")" / nil */
1131         addr = imapx_parse_address_list (is, cancellable, &local_error);
1132         if (addr) {
1133                 addrstr = camel_header_address_list_format (addr);
1134                 minfo->cc = camel_pstring_strdup (addrstr);
1135                 g_free (addrstr);
1136                 camel_header_address_list_clear (&addr);
1137         }
1138
1139         /* we dont keep bcc either */
1140
1141         /* env_bcc         ::= "(" 1*address ")" / nil */
1142         addr = imapx_parse_address_list (is, cancellable, &local_error);
1143         camel_header_address_list_clear (&addr);
1144
1145         /* FIXME: need to put in-reply-to into references hash list */
1146
1147         /* env_in_reply_to ::= nstring */
1148         tok = camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1149
1150         /* FIXME: need to put message-id into message-id hash */
1151
1152         /* env_message_id  ::= nstring */
1153         tok = camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1154
1155         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1156         if (tok != ')') {
1157                 g_clear_error (&local_error);
1158                 camel_message_info_free (minfo);
1159                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting ')'");
1160                 return NULL;
1161         }
1162
1163         /* CHEN TODO handle exceptions better */
1164         if (local_error != NULL)
1165                 g_propagate_error (error, local_error);
1166
1167         return (CamelMessageInfo *) minfo;
1168 }
1169
1170 struct _CamelMessageContentInfo *
1171 imapx_parse_body (CamelIMAPXStream *is,
1172                   GCancellable *cancellable,
1173                   GError **error)
1174 {
1175         gint tok;
1176         guint len;
1177         guchar *token;
1178         struct _CamelMessageContentInfo * cinfo = NULL;
1179         struct _CamelMessageContentInfo *subinfo, *last;
1180         struct _CamelContentDisposition * dinfo = NULL;
1181         GError *local_error = NULL;
1182
1183         /* body            ::= "(" body_type_1part / body_type_mpart ")" */
1184
1185         p (is->tagprefix, "body\n");
1186
1187         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1188         if (tok != '(') {
1189                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "body: expecting '('");
1190                 return NULL;
1191         }
1192
1193         /* 1*body (optional for multiparts) */
1194         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1195         camel_imapx_stream_ungettoken (is, tok, token, len);
1196         if (tok == '(') {
1197                 /* body_type_mpart ::= 1*body SPACE media_subtype
1198                 [SPACE body_ext_mpart] */
1199
1200                 cinfo = g_malloc0 (sizeof (*cinfo));
1201                 last = (struct _CamelMessageContentInfo *) &cinfo->childs;
1202                 do {
1203                         subinfo = imapx_parse_body (is, cancellable, &local_error);
1204                         last->next = subinfo;
1205                         last = subinfo;
1206                         subinfo->parent = cinfo;
1207                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1208                         camel_imapx_stream_ungettoken (is, tok, token, len);
1209                 } while (tok == '(');
1210
1211                 d (is->tagprefix, "media_subtype\n");
1212
1213                 camel_imapx_stream_astring (is, &token, cancellable, &local_error);
1214                 cinfo->type = camel_content_type_new ("multipart", (gchar *) token);
1215
1216                 /* body_ext_mpart  ::= body_fld_param
1217                  * [SPACE body_fld_dsp SPACE body_fld_lang
1218                  * [SPACE 1#body_extension]]
1219                  *    ;; MUST NOT be returned on non-extensible
1220                  *    ;; "BODY" fetch */
1221
1222                 d (is->tagprefix, "body_ext_mpart\n");
1223
1224                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1225                 camel_imapx_stream_ungettoken (is, tok, token, len);
1226                 if (tok == '(') {
1227                         imapx_parse_param_list (is, &cinfo->type->params, cancellable, &local_error);
1228
1229                         /* body_fld_dsp    ::= "(" string SPACE body_fld_param ")" / nil */
1230
1231                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1232                         camel_imapx_stream_ungettoken (is, tok, token, len);
1233                         if (tok == '(' || tok == IMAPX_TOK_TOKEN) {
1234                                 dinfo = imapx_parse_ext_optional (is, cancellable, &local_error);
1235                                 /* other extension fields?, soaked up below */
1236                         } else {
1237                                 camel_imapx_stream_ungettoken (is, tok, token, len);
1238                         }
1239                 }
1240         } else {
1241                 /* body_type_1part ::= (body_type_basic / body_type_msg / body_type_text)
1242                  * [SPACE body_ext_1part]
1243                  *
1244                  * body_type_basic ::= media_basic SPACE body_fields
1245                  * body_type_text  ::= media_text SPACE body_fields SPACE body_fld_lines
1246                  * body_type_msg   ::= media_message SPACE body_fields SPACE envelope
1247                  * SPACE body SPACE body_fld_lines */
1248
1249                 d (is->tagprefix, "Single part body\n");
1250
1251                 cinfo = imapx_parse_body_fields (is, cancellable, &local_error);
1252
1253                 d (is->tagprefix, "envelope?\n");
1254
1255                 /* do we have an envelope following */
1256                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1257                 camel_imapx_stream_ungettoken (is, tok, token, len);
1258                 if (tok == '(') {
1259                         struct _CamelMessageInfo * minfo = NULL;
1260
1261                         /* what do we do with the envelope?? */
1262                         minfo = imapx_parse_envelope (is, cancellable, &local_error);
1263                         /* what do we do with the message content info?? */
1264                         //((CamelMessageInfoBase *) minfo)->content = imapx_parse_body (is);
1265                         camel_message_info_free (minfo);
1266                         minfo = NULL;
1267                         d (is->tagprefix, "Scanned envelope - what do i do with it?\n");
1268                 }
1269
1270                 d (is->tagprefix, "fld_lines?\n");
1271
1272                 /* do we have fld_lines following? */
1273                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1274                 if (tok == IMAPX_TOK_INT) {
1275                         d (is->tagprefix, "field lines: %s\n", token);
1276                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1277                 }
1278                 camel_imapx_stream_ungettoken (is, tok, token, len);
1279
1280                 /* body_ext_1part  ::= body_fld_md5 [SPACE body_fld_dsp
1281                 [SPACE body_fld_lang
1282                 [SPACE 1#body_extension]]]
1283                  * ;; MUST NOT be returned on non - extensible
1284                  * ;; "BODY" fetch */
1285
1286                 d (is->tagprefix, "extension data?\n");
1287
1288                 if (tok != ')') {
1289                         camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1290
1291                         d (is->tagprefix, "md5: %s\n", token ? (gchar *) token:"NIL");
1292
1293                         /* body_fld_dsp    ::= "(" string SPACE body_fld_param ")" / nil */
1294
1295                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1296                         camel_imapx_stream_ungettoken (is, tok, token, len);
1297                         if (tok == '(' || tok == IMAPX_TOK_TOKEN) {
1298                                 dinfo = imapx_parse_ext_optional (is, cancellable, &local_error);
1299                                 /* then other extension fields, soaked up below */
1300                         }
1301                 }
1302         }
1303
1304         /* soak up any other extension fields that may be present */
1305         /* there should only be simple tokens, no lists */
1306         do {
1307                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1308                 if (tok != ')') {
1309                         d (is->tagprefix, "Dropping extension data '%s'\n", token);
1310                 }
1311         } while (tok != ')');
1312
1313         /* CHEN TODO handle exceptions better */
1314         if (local_error != NULL) {
1315                 g_propagate_error (error, local_error);
1316                 if (cinfo)
1317                         imapx_free_body (cinfo);
1318                 if (dinfo)
1319                         camel_content_disposition_unref (dinfo);
1320                 return NULL;
1321         }
1322
1323         /* FIXME: do something with the disposition, currently we have no way to pass it out? */
1324         if (dinfo)
1325                 camel_content_disposition_unref (dinfo);
1326
1327         return cinfo;
1328 }
1329
1330 gchar *
1331 imapx_parse_section (CamelIMAPXStream *is,
1332                      GCancellable *cancellable,
1333                      GError **error)
1334 {
1335         gint tok;
1336         guint len;
1337         guchar *token;
1338         gchar * section = NULL;
1339
1340         /* currently we only return the part within the [section] specifier
1341          * any header fields are parsed, but dropped */
1342
1343         /*
1344          * section         ::= "[" [section_text /
1345          * (nz_number *["." nz_number] ["." (section_text / "MIME")])] "]"
1346          *
1347          * section_text    ::= "HEADER" / "HEADER.FIELDS" [".NOT"]
1348          * SPACE header_list / "TEXT"
1349          */
1350
1351         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1352         if (tok != '[') {
1353                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "section: expecting '['");
1354                 return NULL;
1355         }
1356
1357         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1358         if (tok == IMAPX_TOK_INT || tok == IMAPX_TOK_TOKEN)
1359                 section = g_strdup ((gchar *) token);
1360         else if (tok == ']') {
1361                 section = g_strdup ("");
1362                 camel_imapx_stream_ungettoken (is, tok, token, len);
1363         } else {
1364                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "section: expecting token");
1365                 return NULL;
1366         }
1367
1368         /* header_list     ::= "(" 1#header_fld_name ")"
1369          * header_fld_name ::= astring */
1370
1371         /* we dont need the header specifiers */
1372         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1373         if (tok == '(') {
1374                 do {
1375                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1376                         if (tok == IMAPX_TOK_STRING || tok == IMAPX_TOK_TOKEN || tok == IMAPX_TOK_INT) {
1377                                 /* ?do something? */
1378                         } else if (tok != ')') {
1379                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "section: header fields: expecting string");
1380                                 g_free (section);
1381                                 return NULL;
1382                         }
1383                 } while (tok != ')');
1384                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1385         }
1386
1387         if (tok != ']') {
1388                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "section: expecting ']'");
1389                 g_free (section);
1390                 return NULL;
1391         }
1392
1393         return section;
1394 }
1395
1396 static guint64
1397 imapx_parse_modseq (CamelIMAPXStream *is,
1398                     GCancellable *cancellable,
1399                     GError **error)
1400 {
1401         guint64 ret;
1402         gint tok;
1403         guint len;
1404         guchar *token;
1405
1406         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1407         if (tok != '(') {
1408                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "fetch: expecting '('");
1409                 return 0;
1410         }
1411         ret = camel_imapx_stream_number (is, cancellable, error);
1412         if (ret == 0)
1413                 return 0;
1414
1415         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1416         if (tok != ')') {
1417                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "fetch: expecting '('");
1418                 return 0;
1419         }
1420         return ret;
1421 }
1422
1423 void
1424 imapx_free_fetch (struct _fetch_info *finfo)
1425 {
1426         if (finfo == NULL)
1427                 return;
1428
1429         if (finfo->body)
1430                 g_object_unref (finfo->body);
1431         if (finfo->text)
1432                 g_object_unref (finfo->text);
1433         if (finfo->header)
1434                 g_object_unref (finfo->header);
1435         if (finfo->minfo)
1436                 camel_message_info_free (finfo->minfo);
1437         if (finfo->cinfo)
1438                 imapx_free_body (finfo->cinfo);
1439         camel_flag_list_free (&finfo->user_flags);
1440         g_free (finfo->date);
1441         g_free (finfo->section);
1442         g_free (finfo->uid);
1443         g_free (finfo);
1444 }
1445
1446 /* debug, dump one out */
1447 void
1448 imapx_dump_fetch (struct _fetch_info *finfo)
1449 {
1450         CamelStream *sout;
1451         gchar *string;
1452         gint fd;
1453
1454         d ('?', "Fetch info:\n");
1455         if (finfo == NULL) {
1456                 d ('?', "Empty\n");
1457                 return;
1458         }
1459
1460         fd = dup (1);
1461         sout = camel_stream_fs_new_with_fd (fd);
1462         if (finfo->body) {
1463                 camel_stream_write_string (sout, "Body content:\n", NULL, NULL);
1464                 camel_stream_write_to_stream (finfo->body, sout, NULL, NULL);
1465                 g_seekable_seek (
1466                         G_SEEKABLE (finfo->body),
1467                         0, G_SEEK_SET, NULL, NULL);
1468         }
1469         if (finfo->text) {
1470                 camel_stream_write_string (sout, "Text content:\n", NULL, NULL);
1471                 camel_stream_write_to_stream (finfo->text, sout, NULL, NULL);
1472                 g_seekable_seek (
1473                         G_SEEKABLE (finfo->text),
1474                         0, G_SEEK_SET, NULL, NULL);
1475         }
1476         if (finfo->header) {
1477                 camel_stream_write_string (sout, "Header content:\n", NULL, NULL);
1478                 camel_stream_write_to_stream (finfo->header, sout, NULL, NULL);
1479                 g_seekable_seek (
1480                         G_SEEKABLE (finfo->header),
1481                         0, G_SEEK_SET, NULL, NULL);
1482         }
1483         if (finfo->minfo) {
1484                 camel_stream_write_string (sout, "Message Info:\n", NULL, NULL);
1485                 camel_message_info_dump (finfo->minfo);
1486         }
1487         if (finfo->cinfo) {
1488                 camel_stream_write_string (sout, "Content Info:\n", NULL, NULL);
1489                 //camel_content_info_dump (finfo->cinfo, 0);
1490         }
1491         if (finfo->got & FETCH_SIZE) {
1492                 string = g_strdup_printf ("Size: %d\n", (gint) finfo->size);
1493                 camel_stream_write_string (sout, string, NULL, NULL);
1494                 g_free (string);
1495         }
1496         if (finfo->got & FETCH_BODY) {
1497                 string = g_strdup_printf ("Offset: %d\n", (gint) finfo->offset);
1498                 camel_stream_write_string (sout, string, NULL, NULL);
1499                 g_free (string);
1500         }
1501         if (finfo->got & FETCH_FLAGS) {
1502                 string = g_strdup_printf ("Flags: %08x\n", (gint) finfo->flags);
1503                 camel_stream_write_string (sout, string, NULL, NULL);
1504                 g_free (string);
1505         }
1506         if (finfo->date) {
1507                 string = g_strdup_printf ("Data: '%s'\n", finfo->date);
1508                 camel_stream_write_string (sout, string, NULL, NULL);
1509                 g_free (string);
1510         }
1511         if (finfo->section) {
1512                 string = g_strdup_printf ("Section: '%s'\n", finfo->section);
1513                 camel_stream_write_string (sout, string, NULL, NULL);
1514                 g_free (string);
1515         }
1516         if (finfo->date) {
1517                 string = g_strdup_printf ("UID: '%s'\n", finfo->uid);
1518                 camel_stream_write_string (sout, string, NULL, NULL);
1519                 g_free (string);
1520         }
1521         g_object_unref (sout);
1522 }
1523
1524 struct _fetch_info *
1525 imapx_parse_fetch (CamelIMAPXStream *is,
1526                    GCancellable *cancellable,
1527                    GError **error)
1528 {
1529         gint tok;
1530         guint len;
1531         guchar *token, *p, c;
1532         struct _fetch_info *finfo;
1533
1534         finfo = g_malloc0 (sizeof (*finfo));
1535
1536         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1537         if (tok != '(') {
1538                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "fetch: expecting '('");
1539                 g_free (finfo);
1540                 return NULL;
1541         }
1542
1543         while ((tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL)) == IMAPX_TOK_TOKEN) {
1544
1545                 p = token;
1546                 while ((c=*p))
1547                         *p++ = toupper(c);
1548
1549                 switch (imapx_tokenise ((gchar *) token, len)) {
1550                         case IMAPX_ENVELOPE:
1551                                 finfo->minfo = imapx_parse_envelope (is, cancellable, NULL);
1552                                 finfo->got |= FETCH_MINFO;
1553                                 break;
1554                         case IMAPX_FLAGS:
1555                                 imapx_parse_flags (is, &finfo->flags, &finfo->user_flags, cancellable, NULL);
1556                                 finfo->got |= FETCH_FLAGS;
1557                                 break;
1558                         case IMAPX_INTERNALDATE:
1559                                 camel_imapx_stream_nstring (is, &token, cancellable, NULL);
1560                                 /* TODO: convert to camel format? */
1561                                 finfo->date = g_strdup ((gchar *) token);
1562                                 finfo->got |= FETCH_DATE;
1563                                 break;
1564                         case IMAPX_RFC822_HEADER:
1565                                 camel_imapx_stream_nstring_stream (is, &finfo->header, cancellable, NULL);
1566                                 finfo->got |= FETCH_HEADER;
1567                                 break;
1568                         case IMAPX_RFC822_TEXT:
1569                                 camel_imapx_stream_nstring_stream (is, &finfo->text, cancellable, NULL);
1570                                 finfo->got |= FETCH_TEXT;
1571                                 break;
1572                         case IMAPX_RFC822_SIZE:
1573                                 finfo->size = camel_imapx_stream_number (is, cancellable, NULL);
1574                                 finfo->got |= FETCH_SIZE;
1575                                 break;
1576                         case IMAPX_BODYSTRUCTURE:
1577                                 finfo->cinfo = imapx_parse_body (is, cancellable, NULL);
1578                                 finfo->got |= FETCH_CINFO;
1579                                 break;
1580                         case IMAPX_MODSEQ:
1581                                 finfo->modseq = imapx_parse_modseq (is, cancellable, NULL);
1582                                 finfo->got |= FETCH_MODSEQ;
1583                                 break;
1584                         case IMAPX_BODY:
1585                                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1586                                 camel_imapx_stream_ungettoken (is, tok, token, len);
1587                                 if (tok == '(') {
1588                                         finfo->cinfo = imapx_parse_body (is, cancellable, NULL);
1589                                         finfo->got |= FETCH_CINFO;
1590                                 } else if (tok == '[') {
1591                                         finfo->section = imapx_parse_section (is, cancellable, NULL);
1592                                         finfo->got |= FETCH_SECTION;
1593                                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1594                                         if (token[0] == '<') {
1595                                                 finfo->offset = strtoul ((gchar *) token + 1, NULL, 10);
1596                                         } else {
1597                                                 camel_imapx_stream_ungettoken (is, tok, token, len);
1598                                         }
1599                                         camel_imapx_stream_nstring_stream (is, &finfo->body, cancellable, NULL);
1600                                         finfo->got |= FETCH_BODY;
1601                                 } else {
1602                                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "unknown body response");
1603                                         imapx_free_fetch (finfo);
1604                                         return NULL;
1605                                 }
1606                                 break;
1607                         case IMAPX_UID:
1608                                 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1609                                 if (tok != IMAPX_TOK_INT) {
1610                                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "uid not integer");
1611                                 }
1612
1613                                 finfo->uid = g_strdup ((gchar *) token);
1614                                 finfo->got |= FETCH_UID;
1615                                 break;
1616                         default:
1617                                 imapx_free_fetch (finfo);
1618                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "unknown body response");
1619                                 return NULL;
1620                 }
1621         }
1622
1623         if (tok != ')') {
1624                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "missing closing ')' on fetch response");
1625                 imapx_free_fetch (finfo);
1626                 return NULL;
1627         }
1628
1629         return finfo;
1630 }
1631
1632 struct _state_info *
1633 imapx_parse_status_info (CamelIMAPXStream *is,
1634                          GCancellable *cancellable,
1635                          GError **error)
1636 {
1637         struct _state_info *sinfo;
1638         gint tok;
1639         guint len;
1640         guchar *token;
1641
1642         sinfo = g_malloc0 (sizeof (*sinfo));
1643
1644         /* skip the folder name */
1645         if (camel_imapx_stream_astring (is, &token, cancellable, error)) {
1646                 g_free (sinfo);
1647                 return NULL;
1648         }
1649         sinfo->name = camel_utf7_utf8 ((gchar *) token);
1650
1651         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1652         if (tok != '(') {
1653                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "parse status info: expecting '('");
1654                 g_free (sinfo);
1655                 return NULL;
1656         }
1657
1658         while ((tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL)) == IMAPX_TOK_TOKEN) {
1659                 switch (imapx_tokenise ((gchar *) token, len)) {
1660                         case IMAPX_MESSAGES:
1661                                 sinfo->messages = camel_imapx_stream_number (is, cancellable, NULL);
1662                                 break;
1663                         case IMAPX_RECENT:
1664                                 sinfo->recent = camel_imapx_stream_number (is, cancellable, NULL);
1665                                 break;
1666                         case IMAPX_UIDNEXT:
1667                                 sinfo->uidnext = camel_imapx_stream_number (is, cancellable, NULL);
1668                                 break;
1669                         case IMAPX_UIDVALIDITY:
1670                                 sinfo->uidvalidity = camel_imapx_stream_number (is, cancellable, NULL);
1671                                 break;
1672                         case IMAPX_UNSEEN:
1673                                 sinfo->unseen = camel_imapx_stream_number (is, cancellable, NULL);
1674                                 break;
1675                         case IMAPX_HIGHESTMODSEQ:
1676                                 sinfo->highestmodseq = camel_imapx_stream_number (is, cancellable, NULL);
1677                                 break;
1678                         case IMAPX_NOMODSEQ:
1679                         break;
1680                         default:
1681                                 g_free (sinfo);
1682                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "unknown status response");
1683                                 return NULL;
1684                 }
1685         }
1686
1687         if (tok != ')') {
1688                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "missing closing ')' on status response");
1689                 g_free (sinfo);
1690                 return NULL;
1691         }
1692
1693         return sinfo;
1694 }
1695
1696 static void
1697 generate_uids_from_sequence (GPtrArray *uids,
1698                              guint32 begin_uid,
1699                              guint32 end_uid)
1700 {
1701         guint32 i;
1702
1703         for (i = begin_uid; i <= end_uid; i++)
1704                 g_ptr_array_add (uids, GUINT_TO_POINTER (i));
1705 }
1706
1707 GPtrArray *
1708 imapx_parse_uids (CamelIMAPXStream *is,
1709                   GCancellable *cancellable,
1710                   GError **error)
1711 {
1712         GPtrArray *uids = g_ptr_array_new ();
1713         guchar *token;
1714         gchar **splits;
1715         guint len, str_len;
1716         gint tok, i;
1717
1718         tok = camel_imapx_stream_token (is, &token, &len, cancellable, error);
1719         if (tok < 0)
1720                 return NULL;
1721
1722         splits = g_strsplit ((gchar *) token, ",", -1);
1723         str_len = g_strv_length (splits);
1724
1725         for (i = 0; i < str_len; i++)   {
1726                 if (g_strstr_len (splits[i], -1, ":")) {
1727                         gchar **seq = g_strsplit (splits[i], ":", -1);
1728                         guint32 uid1 = strtoul ((gchar *) seq[0], NULL, 10);
1729                         guint32 uid2 = strtoul ((gchar *) seq[1], NULL, 10);
1730
1731                         generate_uids_from_sequence (uids, uid1, uid2);
1732                         g_strfreev (seq);
1733                 } else {
1734                         guint32 uid = strtoul ((gchar *) splits[i], NULL, 10);
1735                         g_ptr_array_add (uids, GUINT_TO_POINTER (uid));
1736                 }
1737         }
1738
1739         g_strfreev (splits);
1740
1741         return uids;
1742 }
1743
1744 /* rfc 2060 section 7.1 Status Responses */
1745 /* shoudl this start after [ or before the [? token_unget anyone? */
1746 struct _status_info *
1747 imapx_parse_status (CamelIMAPXStream *is,
1748                     GCancellable *cancellable,
1749                     GError **error)
1750 {
1751         gint tok;
1752         guint len;
1753         guchar *token;
1754         struct _status_info *sinfo;
1755
1756         sinfo = g_malloc0 (sizeof (*sinfo));
1757
1758         camel_imapx_stream_atom (is, &token, &len, cancellable, NULL);
1759
1760         /*
1761          * resp_cond_auth  ::= ("OK" / "PREAUTH") SPACE resp_text
1762          * ;; Authentication condition
1763          *
1764          * resp_cond_bye   ::= "BYE" SPACE resp_text
1765          *
1766          * resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text
1767          * ;; Status condition
1768          */
1769
1770         sinfo->result = imapx_tokenise ((gchar *) token, len);
1771         switch (sinfo->result) {
1772                 case IMAPX_OK:
1773                 case IMAPX_NO:
1774                 case IMAPX_BAD:
1775                 case IMAPX_PREAUTH:
1776                 case IMAPX_BYE:
1777                         break;
1778                 default:
1779                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting OK/NO/BAD");
1780                         g_free (sinfo);
1781                         return NULL;
1782         }
1783
1784         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1785         if (tok == '[') {
1786                 camel_imapx_stream_atom (is, &token, &len, cancellable, NULL);
1787                 sinfo->condition = imapx_tokenise ((gchar *) token, len);
1788
1789                 /* parse any details */
1790                 switch (sinfo->condition) {
1791                         case IMAPX_READ_ONLY:
1792                         case IMAPX_READ_WRITE:
1793                         case IMAPX_ALERT:
1794                         case IMAPX_PARSE:
1795                         case IMAPX_TRYCREATE:
1796                         case IMAPX_CLOSED:
1797                                 break;
1798                         case IMAPX_APPENDUID:
1799                                 sinfo->u.appenduid.uidvalidity = camel_imapx_stream_number (is, cancellable, NULL);
1800                                 sinfo->u.appenduid.uid = camel_imapx_stream_number (is, cancellable, NULL);
1801                                 break;
1802                         case IMAPX_COPYUID:
1803                                 sinfo->u.copyuid.uidvalidity = camel_imapx_stream_number (is, cancellable, NULL);
1804                                 sinfo->u.copyuid.uids = imapx_parse_uids (is, cancellable, NULL);
1805                                 sinfo->u.copyuid.copied_uids = imapx_parse_uids (is, cancellable, NULL);
1806                                 break;
1807                         case IMAPX_NEWNAME:
1808                                 /* the rfc doesn't specify the bnf for this */
1809                                 camel_imapx_stream_astring (is, &token, cancellable, NULL);
1810                                 sinfo->u.newname.oldname = g_strdup ((gchar *) token);
1811                                 camel_imapx_stream_astring (is, &token, cancellable, NULL);
1812                                 sinfo->u.newname.newname = g_strdup ((gchar *) token);
1813                                 break;
1814                         case IMAPX_PERMANENTFLAGS:
1815                                 /* we only care about \* for permanent flags, not user flags */
1816                                 imapx_parse_flags (is, &sinfo->u.permanentflags, NULL, cancellable, NULL);
1817                                 break;
1818                         case IMAPX_UIDVALIDITY:
1819                                 sinfo->u.uidvalidity = camel_imapx_stream_number (is, cancellable, NULL);
1820                                 break;
1821                         case IMAPX_UIDNEXT:
1822                                 sinfo->u.uidnext = camel_imapx_stream_number (is, cancellable, NULL);
1823                                 break;
1824                         case IMAPX_UNSEEN:
1825                                 sinfo->u.unseen = camel_imapx_stream_number (is, cancellable, NULL);
1826                                 break;
1827                         case IMAPX_HIGHESTMODSEQ:
1828                                 sinfo->u.highestmodseq = camel_imapx_stream_number (is, cancellable, NULL);
1829                                 break;
1830                         case IMAPX_CAPABILITY:
1831                                 sinfo->u.cinfo = imapx_parse_capability (is, cancellable, NULL);
1832                                 break;
1833                         default:
1834                                 sinfo->condition = IMAPX_UNKNOWN;
1835                                 d (is->tagprefix, "Got unknown response code: %s: ignored\n", token);
1836                 }
1837
1838                 /* ignore anything we dont know about */
1839                 do {
1840                         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1841                         if (tok == '\n' || tok < 0) {
1842                                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "server response truncated");
1843                                 imapx_free_status (sinfo);
1844                                 return NULL;
1845                         }
1846                 } while (tok != ']');
1847         } else {
1848                 camel_imapx_stream_ungettoken (is, tok, token, len);
1849         }
1850
1851         /* and take the human readable response */
1852         camel_imapx_stream_text (is, (guchar **) &sinfo->text, cancellable, NULL);
1853
1854         return sinfo;
1855 }
1856
1857 struct _status_info *
1858 imapx_copy_status (struct _status_info *sinfo)
1859 {
1860         struct _status_info *out;
1861
1862         out = g_malloc (sizeof (*out));
1863         memcpy (out, sinfo, sizeof (*out));
1864         out->text = g_strdup (out->text);
1865         if (out->condition == IMAPX_NEWNAME) {
1866                 out->u.newname.oldname = g_strdup (out->u.newname.oldname);
1867                 out->u.newname.newname = g_strdup (out->u.newname.newname);
1868         }
1869
1870         return out;
1871 }
1872
1873 void
1874 imapx_free_status (struct _status_info *sinfo)
1875 {
1876         if (sinfo == NULL)
1877                 return;
1878
1879         switch (sinfo->condition) {
1880         case IMAPX_NEWNAME:
1881                 g_free (sinfo->u.newname.oldname);
1882                 g_free (sinfo->u.newname.newname);
1883                 break;
1884         case IMAPX_COPYUID:
1885                 g_ptr_array_free (sinfo->u.copyuid.uids, FALSE);
1886                 g_ptr_array_free (sinfo->u.copyuid.copied_uids, FALSE);
1887                 break;
1888         case IMAPX_CAPABILITY:
1889                 if (sinfo->u.cinfo)
1890                         imapx_free_capability (sinfo->u.cinfo);
1891                 break;
1892         default:
1893                 break;
1894         }
1895
1896         g_free (sinfo->text);
1897         g_free (sinfo);
1898 }
1899
1900 /* FIXME: use tokeniser? */
1901 /* FIXME: real flags */
1902 static struct {
1903         const gchar *name;
1904         guint32 flag;
1905 } list_flag_table[] = {
1906         { "\\NOINFERIORS", CAMEL_FOLDER_NOINFERIORS },
1907         { "\\NOSELECT", CAMEL_FOLDER_NOSELECT },
1908         { "\\MARKED", 1<< 16},
1909         { "\\UNMARKED", 1<< 17},
1910         { "\\SUBSCRIBED", CAMEL_FOLDER_SUBSCRIBED },
1911 };
1912
1913 struct _list_info *
1914 imapx_parse_list (CamelIMAPXStream *is,
1915                   GCancellable *cancellable,
1916                   GError **error)
1917 /* throws io, parse */
1918 {
1919         gint tok, i;
1920         guint len;
1921         guchar *token, *p, c;
1922         struct _list_info * linfo;
1923
1924         linfo = g_malloc0 (sizeof (*linfo));
1925
1926         /* mailbox_list    ::= "(" #("\Marked" / "\Noinferiors" /
1927          * "\Noselect" / "\Unmarked" / flag_extension) ")"
1928          * SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox */
1929
1930         tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1931         if (tok != '(') {
1932                 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "list: expecting '('");
1933                 g_free (linfo);
1934                 return NULL;
1935         }
1936
1937         while ((tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL)) != ')') {
1938                 if (tok == IMAPX_TOK_STRING || tok == IMAPX_TOK_TOKEN) {
1939                         p = token;
1940                         while ((c=*p))
1941                                 *p++ = toupper(c);
1942                         for (i = 0; i < G_N_ELEMENTS (list_flag_table); i++)
1943                                 if (!strcmp ((gchar *) token, list_flag_table[i].name))
1944                                         linfo->flags |= list_flag_table[i].flag;
1945                 } else {
1946                         imapx_free_list (linfo);
1947                         g_set_error (error, CAMEL_IMAPX_ERROR, 1, "list: execting flag or ')'");
1948                         return NULL;
1949                 }
1950         }
1951
1952         camel_imapx_stream_nstring (is, &token, cancellable, NULL);
1953         linfo->separator = token?*token : 0;
1954         camel_imapx_stream_astring (is, &token, cancellable, NULL);
1955         linfo->name = camel_utf7_utf8 ((gchar *) token);
1956
1957         return linfo;
1958 }
1959
1960 gchar *
1961 imapx_list_get_path (struct _list_info *li)
1962 {
1963         gchar *path, *p;
1964         gint c;
1965         const gchar *f;
1966
1967         if (li->separator != 0 && li->separator != '/') {
1968                 p = path = alloca (strlen (li->name) * 3 + 1);
1969                 f = li->name;
1970                 while ((c = *f++ & 0xff)) {
1971                         if (c == li->separator)
1972                                 *p++ = '/';
1973                         else if (c == '/' || c == '%')
1974                                 p += sprintf (p, "%%%02X", c);
1975                         else
1976                                 *p++ = c;
1977                 }
1978                 *p = 0;
1979         } else
1980                 path = li->name;
1981
1982         return camel_utf7_utf8 (path);
1983 }
1984
1985 void
1986 imapx_free_list (struct _list_info *linfo)
1987 {
1988         if (linfo) {
1989                 g_free (linfo->name);
1990                 g_free (linfo);
1991         }
1992 }
1993
1994 /* ********************************************************************** */
1995
1996 /*
1997  * From rfc2060
1998  *
1999  * ATOM_CHAR       ::= <any CHAR except atom_specials>
2000  *
2001  * atom_specials   ::= "(" / ")" / "{" / SPACE / CTL / list_wildcards /
2002  *                     quoted_specials
2003  *
2004  * CHAR            ::= <any 7 - bit US - ASCII character except NUL,
2005  *                      0x01 - 0x7f>
2006  *
2007  * CTL             ::= <any ASCII control character and DEL,
2008  *                         0x00 - 0x1f, 0x7f>
2009  *
2010  * SPACE           ::= <ASCII SP, space, 0x20>
2011  *
2012  * list_wildcards  ::= "%" / "*"
2013  *
2014  * quoted_specials ::= <"> / "\"
2015  *
2016  * string          ::= quoted / literal
2017  *
2018  * literal         ::= "{" number "}" CRLF *CHAR8
2019  *                     ;; Number represents the number of CHAR8 octets
2020  *
2021  * quoted          ::= <"> *QUOTED_CHAR <">
2022  *
2023  * QUOTED_CHAR     ::= <any TEXT_CHAR except quoted_specials> /
2024  *                     "\" quoted_specials
2025  *
2026  * TEXT_CHAR       ::= <any CHAR except CR and LF>
2027  */
2028
2029 /*
2030  * ATOM = 1
2031  * SIMPLE? = 2
2032  * NOTID? = 4
2033  *
2034  * QSPECIAL = 8
2035  */
2036
2037 guchar imapx_specials[256] = {
2038 /* 00 */0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 4, 0, 0,
2039 /* 10 */0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2040 /* 20 */4, 1, 0, 1, 1, 0, 1, 1, 0, 0, 2, 7, 1, 1, 1, 1,
2041 /* 30 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2042 /* 40 */7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2043 /* 50 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 0, 7, 1, 1,
2044 /* 60 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2045 /* 70 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0,
2046         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2047         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2048         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2049         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2050         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2051         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2052         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2053         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2054 };
2055
2056 #define list_wildcards "*%"
2057 #define quoted_specials "\\\""
2058 #define atom_specials "(){" list_wildcards quoted_specials /* + CTL */
2059
2060 /* special types for the tokeniser, come out as raw tokens */
2061 #define token_specials "\n*()[]+"
2062 #define notid_specials "\x20\r\n()[]+"
2063
2064 void
2065 imapx_utils_init (void)
2066 {
2067         static gsize imapx_utils_initialized = 0;
2068
2069         if (g_once_init_enter (&imapx_utils_initialized)) {
2070                 gint i;
2071                 guchar v;
2072
2073                 for (i = 0; i < 128; i++) {
2074                         v = 0;
2075                         if (i >= 1 && i <= 0x7f) {
2076                                 v |= IMAPX_TYPE_CHAR;
2077                                 if (i != 0x0a && i != 0x0d) {
2078                                         v |= IMAPX_TYPE_TEXT_CHAR;
2079                                         if (i != '"' && i != '\\')
2080                                                 v |= IMAPX_TYPE_QUOTED_CHAR;
2081                                 }
2082                                 if (i> 0x20 && i <0x7f && strchr (atom_specials, i) == NULL)
2083                                         v |= IMAPX_TYPE_ATOM_CHAR;
2084                                 if (strchr (token_specials, i) != NULL)
2085                                         v |= IMAPX_TYPE_TOKEN_CHAR;
2086                                 if (strchr (notid_specials, i) != NULL)
2087                                         v |= IMAPX_TYPE_NOTID_CHAR;
2088                         }
2089
2090                         imapx_specials[i] = v;
2091                 }
2092
2093                 create_initial_capabilities_table ();
2094                 camel_imapx_set_debug_flags ();
2095
2096                 g_once_init_leave (&imapx_utils_initialized, 1);
2097         }
2098 }
2099
2100 guchar
2101 imapx_is_mask (const gchar *p)
2102 {
2103         guchar v = 0xff;
2104
2105         while (*p) {
2106                 v &= imapx_specials[((guchar) * p) & 0xff];
2107                 p++;
2108         }
2109
2110         return v;
2111 }
2112
2113 gchar *
2114 imapx_path_to_physical (const gchar *prefix,
2115                         const gchar *vpath)
2116 {
2117         GString *out = g_string_new (prefix);
2118         const gchar *p = vpath;
2119         gchar c, *res;
2120
2121         g_string_append_c (out, '/');
2122         p = vpath;
2123         while ((c = *p++)) {
2124                 if (c == '/') {
2125                         g_string_append (out, "/" SUBFOLDER_DIR_NAME "/");
2126                         while (*p == '/')
2127                                 p++;
2128                 } else
2129                         g_string_append_c (out, c);
2130         }
2131
2132         res = out->str;
2133         g_string_free (out, FALSE);
2134
2135         return res;
2136 }
2137
2138 gchar *
2139 imapx_concat (CamelIMAPXStore *imapx_store,
2140               const gchar *prefix,
2141               const gchar *suffix)
2142 {
2143         gsize len;
2144         gchar dir_sep = imapx_store->dir_sep;
2145
2146         if (!dir_sep)
2147                 dir_sep = '/';
2148
2149         len = strlen (prefix);
2150         if (len == 0 || prefix[len - 1] == dir_sep)
2151                 return g_strdup_printf ("%s%s", prefix, suffix);
2152         else
2153                 return g_strdup_printf ("%s%c%s", prefix, dir_sep, suffix);
2154 }
2155
2156 static void
2157 imapx_namespace_clear (CamelIMAPXStoreNamespace **ns)
2158 {
2159         CamelIMAPXStoreNamespace *node, *next;
2160
2161         node = *ns;
2162         while (node != NULL) {
2163                 next = node->next;
2164                 g_free (node->full_name);
2165                 g_free (node->path);
2166                 g_free (node);
2167                 node = next;
2168         }
2169
2170         *ns = NULL;
2171 }
2172
2173 void
2174 camel_imapx_namespace_list_clear (struct _CamelIMAPXNamespaceList *nsl)
2175 {
2176         if (!nsl)
2177                 return;
2178
2179         imapx_namespace_clear (&nsl->personal);
2180         imapx_namespace_clear (&nsl->shared);
2181         imapx_namespace_clear (&nsl->other);
2182
2183         g_free (nsl);
2184         nsl = NULL;
2185 }
2186
2187 static CamelIMAPXStoreNamespace *
2188 imapx_namespace_copy (const CamelIMAPXStoreNamespace *ns)
2189 {
2190         CamelIMAPXStoreNamespace *list, *node, *tail;
2191
2192         list = NULL;
2193         tail = (CamelIMAPXStoreNamespace *) &list;
2194
2195         while (ns != NULL) {
2196                 tail->next = node = g_malloc (sizeof (CamelIMAPXStoreNamespace));
2197                 node->path = g_strdup (ns->path);
2198                 node->sep = ns->sep;
2199                 ns = ns->next;
2200                 tail = node;
2201         }
2202
2203         tail->next = NULL;
2204
2205         return list;
2206 }
2207
2208 struct _CamelIMAPXNamespaceList *
2209 camel_imapx_namespace_list_copy (const struct _CamelIMAPXNamespaceList *nsl)
2210 {
2211         CamelIMAPXNamespaceList *new;
2212
2213         new = g_malloc (sizeof (CamelIMAPXNamespaceList));
2214         new->personal = imapx_namespace_copy (nsl->personal);
2215         new->other = imapx_namespace_copy (nsl->other);
2216         new->shared = imapx_namespace_copy (nsl->shared);
2217
2218         return new;
2219 }
2220
2221 gchar *
2222 imapx_get_temp_uid (void)
2223 {
2224         gchar *res;
2225
2226         static gint counter = 0;
2227         G_LOCK_DEFINE_STATIC (lock);
2228
2229         G_LOCK (lock);
2230         res = g_strdup_printf (
2231                 "tempuid-%lx-%d",
2232                 (gulong) time (NULL),
2233                 counter++);
2234         G_UNLOCK (lock);
2235
2236         return res;
2237 }
2238
2239 void
2240 camel_imapx_destroy_job_queue_info (IMAPXJobQueueInfo *jinfo)
2241 {
2242         g_hash_table_destroy (jinfo->folders);
2243         g_free (jinfo);
2244 }