1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
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.
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.
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.
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"
32 /* high-level parser state */
33 #define p(...) camel_imapx_debug(parse, __VA_ARGS__)
35 #define d(...) camel_imapx_debug(debug, __VA_ARGS__)
37 gint camel_imapx_debug_flags;
38 extern gint camel_verbose_debug;
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; \
46 static void camel_imapx_set_debug_flags (void)
48 if (camel_verbose_debug || camel_debug ("imapx")) {
49 camel_imapx_debug_flags = CAMEL_IMAPX_DEBUG_ALL;
53 debug_set_flag (command);
54 debug_set_flag (debug);
55 debug_set_flag (extra);
57 debug_set_flag (token);
58 debug_set_flag (parse);
59 debug_set_flag (conman);
62 #include "camel-imapx-tokenise.h"
63 #define SUBFOLDER_DIR_NAME "subfolders"
69 imapx_tokenise (register const gchar *str,
72 struct _imapx_keyword *k = imapx_tokenise_struct (str, len);
79 static void imapx_namespace_clear (CamelIMAPXStoreNamespace **ns);
80 static const gchar * rename_label_flag (const gchar *flag, gint len, gboolean server_to_evo);
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 }
99 * should this be part of imapx-driver? */
100 /* maybe this should be a stream op? */
102 imapx_parse_flags (CamelIMAPXStream *stream,
104 CamelFlag **user_flagsp,
105 GCancellable *cancellable,
107 /* throws IO,PARSE exception */
116 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
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);
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;
129 const gchar *flag_name = rename_label_flag ((gchar *) token, strlen ((gchar *) token), TRUE);
131 camel_flag_set (user_flagsp, flag_name, TRUE);
135 tok = tok; /* fixes stupid warning */
137 } else if (tok != ')') {
138 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting flag");
141 } while (tok != ')');
143 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "execting flag list");
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.
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.
161 rename_label_flag (const gchar *flag,
163 gboolean server_to_evo)
166 const gchar *labels[] = {
167 "$Label1", "$Labelimportant",
168 "$Label2", "$Labelwork",
169 "$Label3", "$Labelpersonal",
170 "$Label4", "$Labeltodo",
171 "$Label5", "$Labellater",
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)
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)];
188 imapx_write_flags (GString *string,
190 CamelFlag *user_flags)
193 gboolean first = TRUE;
195 g_string_append_c (string, '(');
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)
202 g_string_append_c (string, ' ');
204 g_string_append (string, flag_table[i].name);
206 flags &= ~flag_table[i].flag;
211 const gchar *flag_name;
213 flag_name = rename_label_flag (
214 user_flags->name, strlen (user_flags->name), FALSE);
217 g_string_append_c (string, ' ');
219 g_string_append (string, flag_name);
221 user_flags = user_flags->next;
224 g_string_append_c (string, ')');
228 imapx_update_user_flags (CamelMessageInfo *info,
229 CamelFlag *server_user_flags)
231 gboolean changed = FALSE;
232 CamelMessageInfoBase *binfo = (CamelMessageInfoBase *) info;
233 CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) info;
234 gboolean set_cal = FALSE;
236 if (camel_flag_get (&binfo->user_flags, "$has_cal"))
239 changed = camel_flag_list_copy (&binfo->user_flags, &server_user_flags);
240 camel_flag_list_copy (&xinfo->server_user_flags, &server_user_flags);
242 /* reset the calendar flag if it was set in messageinfo before */
244 camel_flag_set (&binfo->user_flags, "$has_cal", TRUE);
250 imapx_update_message_info_flags (CamelMessageInfo *info,
251 guint32 server_flags,
252 CamelFlag *server_user_flags,
253 guint32 permanent_flags,
255 gboolean unsolicited)
257 gboolean changed = FALSE;
258 CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) info;
260 if (server_flags != xinfo->server_flags) {
261 guint32 server_set, server_cleared;
263 server_set = server_flags & ~xinfo->server_flags;
264 server_cleared = xinfo->server_flags & ~server_flags;
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;
272 camel_message_info_set_flags ((CamelMessageInfo *) xinfo, server_set | server_cleared, (xinfo->info.flags | server_set) & ~server_cleared);
274 xinfo->server_flags = server_flags;
275 xinfo->info.flags = xinfo->info.flags & ~CAMEL_MESSAGE_FOLDER_FLAGGED;
276 xinfo->info.dirty = TRUE;
281 if ((folder->permanent_flags & CAMEL_MESSAGE_USER) != 0 && imapx_update_user_flags (info, server_user_flags))
288 imapx_set_message_info_flags_for_new_message (CamelMessageInfo *info,
289 guint32 server_flags,
290 CamelFlag *server_user_flags,
293 CamelMessageInfoBase *binfo = (CamelMessageInfoBase *) info;
294 CamelIMAPXMessageInfo *xinfo = (CamelIMAPXMessageInfo *) info;
296 binfo->flags |= server_flags;
297 camel_message_info_set_flags (info, server_flags, binfo->flags | server_flags);
299 xinfo->server_flags = server_flags;
301 if (folder->permanent_flags & CAMEL_MESSAGE_USER)
302 imapx_update_user_flags (info, server_user_flags);
304 binfo->flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
309 imapx_update_store_summary (CamelFolder *folder)
312 CamelStore *parent_store;
313 const gchar *full_name;
314 CamelService *service;
315 CamelSettings *settings;
316 gboolean mobile_mode;
318 full_name = camel_folder_get_full_name (folder);
319 parent_store = camel_folder_get_parent_store (folder);
320 service = CAMEL_SERVICE (parent_store);
322 settings = camel_service_ref_settings (service);
324 mobile_mode = camel_imapx_settings_get_mobile_mode (
325 CAMEL_IMAPX_SETTINGS (settings));
327 g_object_unref (settings);
329 si = camel_store_summary_path ((CamelStoreSummary *) ((CamelIMAPXStore *) parent_store)->summary, full_name);
331 guint32 unread, total;
333 total = camel_folder_summary_count (folder->summary);
334 unread = camel_folder_summary_get_unread_count (folder->summary);
336 if (si->unread != unread || si->total != total) {
341 si->unread = ((CamelIMAPXFolder *) folder)->unread_on_server;
344 camel_store_summary_touch ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
345 camel_store_summary_save ((CamelStoreSummary *)((CamelIMAPXStore *) parent_store)->summary);
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
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 },
376 static GMutex capa_htable_lock; /* capabilities lookup table lock */
377 static GHashTable *capa_htable = NULL; /* capabilities lookup table (extensible) */
380 create_initial_capabilities_table (void)
384 /* call within g_init_once() only,
385 * or require table lock
388 /* TODO add imapx_utils_uninit()
391 capa_htable = g_hash_table_new_full (
397 for (i = 0; i < G_N_ELEMENTS (capa_table); i++) {
398 g_hash_table_insert (
400 g_strdup (capa_table[i].name),
401 GUINT_TO_POINTER (capa_table[i].flag));
405 struct _capability_info *
406 imapx_parse_capability (CamelIMAPXStream *stream,
407 GCancellable *cancellable,
412 guchar *token, *p, c;
413 gboolean free_token = FALSE;
414 struct _capability_info * cinfo;
415 GError *local_error = NULL;
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);
420 /* FIXME: handle auth types */
421 while ((tok = camel_imapx_stream_token (stream, &token, &len, cancellable, &local_error)) != '\n' &&
422 local_error == NULL) {
425 /* Put it back so that imapx_untagged() isn't unhappy */
426 camel_imapx_stream_ungettoken (stream, tok, token, len);
429 token = (guchar *) g_strconcat ((gchar *) token, "+", NULL);
431 case IMAPX_TOK_TOKEN:
432 case IMAPX_TOK_STRING:
436 if (!strncmp ((gchar *) token, "AUTH=", 5)) {
437 g_hash_table_insert (
439 g_strdup ((gchar *) token + 5),
440 GINT_TO_POINTER (1));
444 d (stream->tagprefix, " cap: '%s'\n", token);
445 cinfo->capa |= imapx_lookup_capability ((gchar *) token);
453 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "capability: expecting name");
458 if (local_error != NULL) {
459 g_propagate_error (error, local_error);
460 imapx_free_capability (cinfo);
467 void imapx_free_capability (struct _capability_info *cinfo)
469 g_hash_table_destroy (cinfo->auth_types);
474 imapx_register_capability (const gchar *capability)
477 guint64 check_id = 0;
479 GList *tmp_vals = NULL;
481 g_return_val_if_fail (capability != NULL, 0);
483 g_mutex_lock (&capa_htable_lock);
485 /* we rely on IMAP being the first flag, non-zero value
486 * (1 << 0), so we can use GPOINTER_TO_UINT (NULL) as
489 capa_id = GPOINTER_TO_UINT (
490 g_hash_table_lookup (capa_htable, capability));
494 /* not yet there, find biggest flag so far */
495 vals = g_hash_table_get_values (capa_htable);
497 while (tmp_vals != NULL) {
498 guint32 tmp_id = GPOINTER_TO_UINT (tmp_vals->data);
499 if (capa_id < tmp_id)
501 tmp_vals = g_list_next (tmp_vals);
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;
511 g_hash_table_insert (
513 g_strdup (capability),
514 GUINT_TO_POINTER (capa_id));
517 g_mutex_unlock (&capa_htable_lock);
523 imapx_lookup_capability (const gchar *capability)
527 g_return_val_if_fail (capability != NULL, 0);
529 g_mutex_lock (&capa_htable_lock);
531 data = g_hash_table_lookup (capa_htable, capability);
533 g_mutex_unlock (&capa_htable_lock);
535 return GPOINTER_TO_UINT (data);
538 struct _CamelIMAPXNamespaceList *
539 imapx_parse_namespace_list (CamelIMAPXStream *stream,
540 GCancellable *cancellable,
543 CamelIMAPXStoreNamespace *namespaces[3], *node, *tail;
544 CamelIMAPXNamespaceList *nsl = NULL;
550 nsl = g_malloc0 (sizeof (CamelIMAPXNamespaceList));
551 nsl->personal = NULL;
555 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
557 namespaces[n] = NULL;
558 tail = (CamelIMAPXStoreNamespace *) &namespaces[n];
561 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
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");
570 node = g_new0 (CamelIMAPXStoreNamespace, 1);
572 node->path = g_strdup ((gchar *) token);
574 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
576 if (tok == IMAPX_TOK_STRING) {
577 if (strlen ((gchar *) token) == 1) {
581 node->sep = node->path[strlen (node->path) - 1];
585 } else if (tok == IMAPX_TOK_TOKEN) {
586 /* will a NIL be possible here? */
589 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "namespace: expected a string separtor");
598 if (*node->path && node->path[strlen (node->path) -1] == node->sep)
599 node->path[strlen (node->path) - 1] = '\0';
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);
605 /* TODO remove full_name later. not required */
606 node->full_name = g_strdup (node->path);
608 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
610 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "namespace: expected a ')'");
614 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
618 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "namespace: expected a ')'");
622 } else if (tok == IMAPX_TOK_TOKEN && !strcmp ((gchar *) token, "NIL")) {
623 namespaces[n] = NULL;
625 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "namespace: expected either a '(' or NIL");
629 tok = camel_imapx_stream_token (stream, &token, &len, cancellable, NULL);
633 nsl->personal = namespaces[0];
634 nsl->shared = namespaces[1];
635 nsl->other = namespaces[2];
640 for (i = 0; i < 3; i++)
641 imapx_namespace_clear (&namespaces[i]);
647 * body ::= "(" body_type_1part / body_type_mpart ")"
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.
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
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
669 * body_fields ::= body_fld_param SPACE body_fld_id SPACE
670 * body_fld_desc SPACE body_fld_enc SPACE
673 * body_fld_desc ::= nstring
675 * body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil
677 * body_fld_enc ::= (<"> ("7BIT" / "8BIT" / "BINARY" / "BASE64"/
678 * "QUOTED-PRINTABLE") <">) / string
680 * body_fld_id ::= nstring
682 * body_fld_lang ::= nstring / "(" 1#string ")"
684 * body_fld_lines ::= number
686 * body_fld_md5 ::= nstring
688 * body_fld_octets ::= number
690 * body_fld_param ::= "(" 1#(string SPACE string) ")" / nil
692 * body_type_1part ::= (body_type_basic / body_type_msg / body_type_text)
693 * [SPACE body_ext_1part]
695 * body_type_basic ::= media_basic SPACE body_fields
696 * ;; MESSAGE subtype MUST NOT be "RFC822"
698 * body_type_mpart ::= 1*body SPACE media_subtype
699 * [SPACE body_ext_mpart]
701 * body_type_msg ::= media_message SPACE body_fields SPACE envelope
702 * SPACE body SPACE body_fld_lines
704 * body_type_text ::= media_text SPACE body_fields SPACE body_fld_lines
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 ")"
711 * env_bcc ::= "(" 1*address ")" / nil
713 * env_cc ::= "(" 1*address ")" / nil
715 * env_date ::= nstring
717 * env_from ::= "(" 1*address ")" / nil
719 * env_in_reply_to ::= nstring
721 * env_message_id ::= nstring
723 * env_reply_to ::= "(" 1*address ")" / nil
725 * env_sender ::= "(" 1*address ")" / nil
727 * env_subject ::= nstring
729 * env_to ::= "(" 1*address ")" / nil
731 * media_basic ::= (<"> ("APPLICATION" / "AUDIO" / "IMAGE" /
732 * "MESSAGE" / "VIDEO") <">) / string)
733 * SPACE media_subtype
734 * ;; Defined in[MIME-IMT]
736 * media_message ::= <"> "MESSAGE" <"> SPACE <"> "RFC822" <">
737 * ;; Defined in[MIME-IMT]
739 * media_subtype ::= string
740 * ;; Defined in[MIME-IMT]
742 * media_text ::= <"> "TEXT" <"> SPACE media_subtype
743 * ;; Defined in[MIME-IMT]
745 * ( "type" "subtype" body_fields [envelope body body_fld_lines]
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"))
757 struct _body_fields {
758 CamelContentType *ct;
760 CamelTransferEncoding encoding;
765 imapx_free_body (struct _CamelMessageContentInfo *cinfo)
767 struct _CamelMessageContentInfo *list, *next;
769 list = cinfo->childs;
772 imapx_free_body (list);
777 camel_content_type_unref (cinfo->type);
779 g_free (cinfo->description);
780 g_free (cinfo->encoding);
785 imapx_parse_param_list (CamelIMAPXStream *is,
786 struct _camel_header_param **plist,
787 GCancellable *cancellable,
795 p (is->tagprefix, "body_fld_param\n");
797 /* body_fld_param ::= "(" 1#(string SPACE string) ")" / nil */
798 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
801 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
804 camel_imapx_stream_ungettoken (is, tok, token, len);
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);
812 } /* else check nil? no need */
817 struct _CamelContentDisposition *
818 imapx_parse_ext_optional (CamelIMAPXStream *is,
819 GCancellable *cancellable,
825 struct _CamelContentDisposition *dinfo = NULL;
826 GError *local_error = NULL;
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 */
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
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
843 /* body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil */
845 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
848 dinfo = g_malloc0 (sizeof (*dinfo));
850 /* should be string */
851 camel_imapx_stream_astring (is, &token, cancellable, NULL);
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");
859 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "body_fld_disp: expecting nil or list");
863 p (is->tagprefix, "body_fld_lang\n");
865 /* body_fld_lang ::= nstring / "(" 1#string ")" */
867 /* we just drop the lang string/list, save it somewhere? */
869 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
873 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
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");
883 case IMAPX_TOK_TOKEN:
884 d (is->tagprefix, "body_fld_lang = nil\n");
887 case IMAPX_TOK_STRING:
888 /* we have a string */
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);
900 if (local_error != NULL) {
901 g_propagate_error (error, local_error);
903 camel_content_disposition_unref (dinfo);
910 struct _CamelMessageContentInfo *
911 imapx_parse_body_fields (CamelIMAPXStream *is,
912 GCancellable *cancellable,
917 struct _CamelMessageContentInfo *cinfo;
918 GError *local_error = NULL;
920 /* body_fields ::= body_fld_param SPACE body_fld_id SPACE
921 * body_fld_desc SPACE body_fld_enc SPACE
924 p (is->tagprefix, "body_fields\n");
926 cinfo = g_malloc0 (sizeof (*cinfo));
928 /* this should be string not astring */
929 if (camel_imapx_stream_astring (is, &token, cancellable, error))
931 type = alloca (strlen ( (gchar *) token) + 1);
932 strcpy (type, (gchar *) token);
933 if (camel_imapx_stream_astring (is, &token, cancellable, error))
935 cinfo->type = camel_content_type_new (type, (gchar *) token);
936 if (!imapx_parse_param_list (is, &cinfo->type->params, cancellable, error))
939 /* body_fld_id ::= nstring */
940 if (!camel_imapx_stream_nstring (is, &token, cancellable, error))
942 cinfo->id = g_strdup ((gchar *) token);
944 /* body_fld_desc ::= nstring */
945 if (!camel_imapx_stream_nstring (is, &token, cancellable, error))
947 cinfo->description = g_strdup ((gchar *) token);
949 /* body_fld_enc ::= (<"> ("7BIT" / "8BIT" / "BINARY" / "BASE64"/
950 * "QUOTED-PRINTABLE") <">) / string */
951 if (camel_imapx_stream_astring (is, &token, cancellable, error))
953 cinfo->encoding = g_strdup ((gchar *) token);
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);
964 imapx_free_body (cinfo);
968 struct _camel_header_address *
969 imapx_parse_address_list (CamelIMAPXStream *is,
970 GCancellable *cancellable,
972 /* throws PARSE,IO exception */
976 guchar *token, *host;
978 struct _camel_header_address *list = NULL;
979 GError *local_error = NULL;
981 /* "(" 1*address ")" / nil */
983 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
985 struct _camel_header_address *addr, *group = NULL;
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);
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");
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);
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. */
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);
1021 d (is->tagprefix, "adding group '%s'\n", mbox);
1022 g_free (addr->name);
1024 addr->type = CAMEL_HEADER_ADDRESS_GROUP;
1025 camel_header_address_list_append (&list, addr);
1029 addr->v.addr = g_strdup_printf ("%s@%s", mbox? mbox :"", (const gchar *) host);
1031 d (is->tagprefix, "adding address '%s'\n", addr->v.addr);
1033 camel_header_address_add_member (group, addr);
1035 camel_header_address_list_append (&list, addr);
1038 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1039 } while (tok != ')');
1042 d (is->tagprefix, "empty, nil '%s'\n", token);
1045 /* CHEN TODO handle exception at required places */
1046 if (local_error != NULL)
1047 g_propagate_error (error, local_error);
1052 struct _CamelMessageInfo *
1053 imapx_parse_envelope (CamelIMAPXStream *is,
1054 GCancellable *cancellable,
1060 struct _camel_header_address *addr, *addr_from;
1062 struct _CamelMessageInfoBase *minfo;
1063 GError *local_error = NULL;
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 ")" */
1070 p (is->tagprefix, "envelope\n");
1072 minfo = (CamelMessageInfoBase *) camel_message_info_new (NULL);
1074 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1076 g_clear_error (&local_error);
1077 camel_message_info_free (minfo);
1078 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "envelope: expecting '('");
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);
1086 /* env_subject ::= nstring */
1087 tok = camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1088 minfo->subject = camel_pstring_strdup ((gchar *) token);
1090 /* we merge from/sender into from, append should probably merge more smartly? */
1092 /* env_from ::= "(" 1*address ")" / nil */
1093 addr_from = imapx_parse_address_list (is, cancellable, &local_error);
1095 /* env_sender ::= "(" 1*address ")" / nil */
1096 addr = imapx_parse_address_list (is, cancellable, &local_error);
1098 camel_header_address_list_clear (&addr);
1101 camel_header_address_list_append_list (&addr_from, &addr);
1109 addrstr = camel_header_address_list_format (addr_from);
1110 minfo->from = camel_pstring_strdup (addrstr);
1112 camel_header_address_list_clear (&addr_from);
1115 /* we dont keep reply_to */
1117 /* env_reply_to ::= "(" 1*address ")" / nil */
1118 addr = imapx_parse_address_list (is, cancellable, &local_error);
1119 camel_header_address_list_clear (&addr);
1121 /* env_to ::= "(" 1*address ")" / nil */
1122 addr = imapx_parse_address_list (is, cancellable, &local_error);
1124 addrstr = camel_header_address_list_format (addr);
1125 minfo->to = camel_pstring_strdup (addrstr);
1127 camel_header_address_list_clear (&addr);
1130 /* env_cc ::= "(" 1*address ")" / nil */
1131 addr = imapx_parse_address_list (is, cancellable, &local_error);
1133 addrstr = camel_header_address_list_format (addr);
1134 minfo->cc = camel_pstring_strdup (addrstr);
1136 camel_header_address_list_clear (&addr);
1139 /* we dont keep bcc either */
1141 /* env_bcc ::= "(" 1*address ")" / nil */
1142 addr = imapx_parse_address_list (is, cancellable, &local_error);
1143 camel_header_address_list_clear (&addr);
1145 /* FIXME: need to put in-reply-to into references hash list */
1147 /* env_in_reply_to ::= nstring */
1148 tok = camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1150 /* FIXME: need to put message-id into message-id hash */
1152 /* env_message_id ::= nstring */
1153 tok = camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1155 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1157 g_clear_error (&local_error);
1158 camel_message_info_free (minfo);
1159 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting ')'");
1163 /* CHEN TODO handle exceptions better */
1164 if (local_error != NULL)
1165 g_propagate_error (error, local_error);
1167 return (CamelMessageInfo *) minfo;
1170 struct _CamelMessageContentInfo *
1171 imapx_parse_body (CamelIMAPXStream *is,
1172 GCancellable *cancellable,
1178 struct _CamelMessageContentInfo * cinfo = NULL;
1179 struct _CamelMessageContentInfo *subinfo, *last;
1180 struct _CamelContentDisposition * dinfo = NULL;
1181 GError *local_error = NULL;
1183 /* body ::= "(" body_type_1part / body_type_mpart ")" */
1185 p (is->tagprefix, "body\n");
1187 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1189 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "body: expecting '('");
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);
1197 /* body_type_mpart ::= 1*body SPACE media_subtype
1198 [SPACE body_ext_mpart] */
1200 cinfo = g_malloc0 (sizeof (*cinfo));
1201 last = (struct _CamelMessageContentInfo *) &cinfo->childs;
1203 subinfo = imapx_parse_body (is, cancellable, &local_error);
1204 last->next = 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 == '(');
1211 d (is->tagprefix, "media_subtype\n");
1213 camel_imapx_stream_astring (is, &token, cancellable, &local_error);
1214 cinfo->type = camel_content_type_new ("multipart", (gchar *) token);
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 */
1222 d (is->tagprefix, "body_ext_mpart\n");
1224 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1225 camel_imapx_stream_ungettoken (is, tok, token, len);
1227 imapx_parse_param_list (is, &cinfo->type->params, cancellable, &local_error);
1229 /* body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil */
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 */
1237 camel_imapx_stream_ungettoken (is, tok, token, len);
1241 /* body_type_1part ::= (body_type_basic / body_type_msg / body_type_text)
1242 * [SPACE body_ext_1part]
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 */
1249 d (is->tagprefix, "Single part body\n");
1251 cinfo = imapx_parse_body_fields (is, cancellable, &local_error);
1253 d (is->tagprefix, "envelope?\n");
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);
1259 struct _CamelMessageInfo * minfo = NULL;
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);
1267 d (is->tagprefix, "Scanned envelope - what do i do with it?\n");
1270 d (is->tagprefix, "fld_lines?\n");
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);
1278 camel_imapx_stream_ungettoken (is, tok, token, len);
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 */
1286 d (is->tagprefix, "extension data?\n");
1289 camel_imapx_stream_nstring (is, &token, cancellable, &local_error);
1291 d (is->tagprefix, "md5: %s\n", token ? (gchar *) token:"NIL");
1293 /* body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil */
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 */
1304 /* soak up any other extension fields that may be present */
1305 /* there should only be simple tokens, no lists */
1307 tok = camel_imapx_stream_token (is, &token, &len, cancellable, &local_error);
1309 d (is->tagprefix, "Dropping extension data '%s'\n", token);
1311 } while (tok != ')');
1313 /* CHEN TODO handle exceptions better */
1314 if (local_error != NULL) {
1315 g_propagate_error (error, local_error);
1317 imapx_free_body (cinfo);
1319 camel_content_disposition_unref (dinfo);
1323 /* FIXME: do something with the disposition, currently we have no way to pass it out? */
1325 camel_content_disposition_unref (dinfo);
1331 imapx_parse_section (CamelIMAPXStream *is,
1332 GCancellable *cancellable,
1338 gchar * section = NULL;
1340 /* currently we only return the part within the [section] specifier
1341 * any header fields are parsed, but dropped */
1344 * section ::= "[" [section_text /
1345 * (nz_number *["." nz_number] ["." (section_text / "MIME")])] "]"
1347 * section_text ::= "HEADER" / "HEADER.FIELDS" [".NOT"]
1348 * SPACE header_list / "TEXT"
1351 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1353 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "section: expecting '['");
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);
1364 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "section: expecting token");
1368 /* header_list ::= "(" 1#header_fld_name ")"
1369 * header_fld_name ::= astring */
1371 /* we dont need the header specifiers */
1372 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
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");
1383 } while (tok != ')');
1384 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1388 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "section: expecting ']'");
1397 imapx_parse_modseq (CamelIMAPXStream *is,
1398 GCancellable *cancellable,
1406 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1408 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "fetch: expecting '('");
1411 ret = camel_imapx_stream_number (is, cancellable, error);
1415 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1417 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "fetch: expecting '('");
1424 imapx_free_fetch (struct _fetch_info *finfo)
1430 g_object_unref (finfo->body);
1432 g_object_unref (finfo->text);
1434 g_object_unref (finfo->header);
1436 camel_message_info_free (finfo->minfo);
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);
1446 /* debug, dump one out */
1448 imapx_dump_fetch (struct _fetch_info *finfo)
1454 d ('?', "Fetch info:\n");
1455 if (finfo == NULL) {
1461 sout = camel_stream_fs_new_with_fd (fd);
1463 camel_stream_write_string (sout, "Body content:\n", NULL, NULL);
1464 camel_stream_write_to_stream (finfo->body, sout, NULL, NULL);
1466 G_SEEKABLE (finfo->body),
1467 0, G_SEEK_SET, NULL, NULL);
1470 camel_stream_write_string (sout, "Text content:\n", NULL, NULL);
1471 camel_stream_write_to_stream (finfo->text, sout, NULL, NULL);
1473 G_SEEKABLE (finfo->text),
1474 0, G_SEEK_SET, NULL, NULL);
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);
1480 G_SEEKABLE (finfo->header),
1481 0, G_SEEK_SET, NULL, NULL);
1484 camel_stream_write_string (sout, "Message Info:\n", NULL, NULL);
1485 camel_message_info_dump (finfo->minfo);
1488 camel_stream_write_string (sout, "Content Info:\n", NULL, NULL);
1489 //camel_content_info_dump (finfo->cinfo, 0);
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);
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);
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);
1507 string = g_strdup_printf ("Data: '%s'\n", finfo->date);
1508 camel_stream_write_string (sout, string, NULL, NULL);
1511 if (finfo->section) {
1512 string = g_strdup_printf ("Section: '%s'\n", finfo->section);
1513 camel_stream_write_string (sout, string, NULL, NULL);
1517 string = g_strdup_printf ("UID: '%s'\n", finfo->uid);
1518 camel_stream_write_string (sout, string, NULL, NULL);
1521 g_object_unref (sout);
1524 struct _fetch_info *
1525 imapx_parse_fetch (CamelIMAPXStream *is,
1526 GCancellable *cancellable,
1531 guchar *token, *p, c;
1532 struct _fetch_info *finfo;
1534 finfo = g_malloc0 (sizeof (*finfo));
1536 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1538 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "fetch: expecting '('");
1543 while ((tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL)) == IMAPX_TOK_TOKEN) {
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;
1555 imapx_parse_flags (is, &finfo->flags, &finfo->user_flags, cancellable, NULL);
1556 finfo->got |= FETCH_FLAGS;
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;
1564 case IMAPX_RFC822_HEADER:
1565 camel_imapx_stream_nstring_stream (is, &finfo->header, cancellable, NULL);
1566 finfo->got |= FETCH_HEADER;
1568 case IMAPX_RFC822_TEXT:
1569 camel_imapx_stream_nstring_stream (is, &finfo->text, cancellable, NULL);
1570 finfo->got |= FETCH_TEXT;
1572 case IMAPX_RFC822_SIZE:
1573 finfo->size = camel_imapx_stream_number (is, cancellable, NULL);
1574 finfo->got |= FETCH_SIZE;
1576 case IMAPX_BODYSTRUCTURE:
1577 finfo->cinfo = imapx_parse_body (is, cancellable, NULL);
1578 finfo->got |= FETCH_CINFO;
1581 finfo->modseq = imapx_parse_modseq (is, cancellable, NULL);
1582 finfo->got |= FETCH_MODSEQ;
1585 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1586 camel_imapx_stream_ungettoken (is, tok, token, len);
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);
1597 camel_imapx_stream_ungettoken (is, tok, token, len);
1599 camel_imapx_stream_nstring_stream (is, &finfo->body, cancellable, NULL);
1600 finfo->got |= FETCH_BODY;
1602 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "unknown body response");
1603 imapx_free_fetch (finfo);
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");
1613 finfo->uid = g_strdup ((gchar *) token);
1614 finfo->got |= FETCH_UID;
1617 imapx_free_fetch (finfo);
1618 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "unknown body response");
1624 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "missing closing ')' on fetch response");
1625 imapx_free_fetch (finfo);
1632 struct _state_info *
1633 imapx_parse_status_info (CamelIMAPXStream *is,
1634 GCancellable *cancellable,
1637 struct _state_info *sinfo;
1642 sinfo = g_malloc0 (sizeof (*sinfo));
1644 /* skip the folder name */
1645 if (camel_imapx_stream_astring (is, &token, cancellable, error)) {
1649 sinfo->name = camel_utf7_utf8 ((gchar *) token);
1651 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1653 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "parse status info: expecting '('");
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);
1664 sinfo->recent = camel_imapx_stream_number (is, cancellable, NULL);
1667 sinfo->uidnext = camel_imapx_stream_number (is, cancellable, NULL);
1669 case IMAPX_UIDVALIDITY:
1670 sinfo->uidvalidity = camel_imapx_stream_number (is, cancellable, NULL);
1673 sinfo->unseen = camel_imapx_stream_number (is, cancellable, NULL);
1675 case IMAPX_HIGHESTMODSEQ:
1676 sinfo->highestmodseq = camel_imapx_stream_number (is, cancellable, NULL);
1678 case IMAPX_NOMODSEQ:
1682 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "unknown status response");
1688 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "missing closing ')' on status response");
1697 generate_uids_from_sequence (GPtrArray *uids,
1703 for (i = begin_uid; i <= end_uid; i++)
1704 g_ptr_array_add (uids, GUINT_TO_POINTER (i));
1708 imapx_parse_uids (CamelIMAPXStream *is,
1709 GCancellable *cancellable,
1712 GPtrArray *uids = g_ptr_array_new ();
1718 tok = camel_imapx_stream_token (is, &token, &len, cancellable, error);
1722 splits = g_strsplit ((gchar *) token, ",", -1);
1723 str_len = g_strv_length (splits);
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);
1731 generate_uids_from_sequence (uids, uid1, uid2);
1734 guint32 uid = strtoul ((gchar *) splits[i], NULL, 10);
1735 g_ptr_array_add (uids, GUINT_TO_POINTER (uid));
1739 g_strfreev (splits);
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,
1754 struct _status_info *sinfo;
1756 sinfo = g_malloc0 (sizeof (*sinfo));
1758 camel_imapx_stream_atom (is, &token, &len, cancellable, NULL);
1761 * resp_cond_auth ::= ("OK" / "PREAUTH") SPACE resp_text
1762 * ;; Authentication condition
1764 * resp_cond_bye ::= "BYE" SPACE resp_text
1766 * resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text
1767 * ;; Status condition
1770 sinfo->result = imapx_tokenise ((gchar *) token, len);
1771 switch (sinfo->result) {
1779 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "expecting OK/NO/BAD");
1784 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1786 camel_imapx_stream_atom (is, &token, &len, cancellable, NULL);
1787 sinfo->condition = imapx_tokenise ((gchar *) token, len);
1789 /* parse any details */
1790 switch (sinfo->condition) {
1791 case IMAPX_READ_ONLY:
1792 case IMAPX_READ_WRITE:
1795 case IMAPX_TRYCREATE:
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);
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);
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);
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);
1818 case IMAPX_UIDVALIDITY:
1819 sinfo->u.uidvalidity = camel_imapx_stream_number (is, cancellable, NULL);
1822 sinfo->u.uidnext = camel_imapx_stream_number (is, cancellable, NULL);
1825 sinfo->u.unseen = camel_imapx_stream_number (is, cancellable, NULL);
1827 case IMAPX_HIGHESTMODSEQ:
1828 sinfo->u.highestmodseq = camel_imapx_stream_number (is, cancellable, NULL);
1830 case IMAPX_CAPABILITY:
1831 sinfo->u.cinfo = imapx_parse_capability (is, cancellable, NULL);
1834 sinfo->condition = IMAPX_UNKNOWN;
1835 d (is->tagprefix, "Got unknown response code: %s: ignored\n", token);
1838 /* ignore anything we dont know about */
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);
1846 } while (tok != ']');
1848 camel_imapx_stream_ungettoken (is, tok, token, len);
1851 /* and take the human readable response */
1852 camel_imapx_stream_text (is, (guchar **) &sinfo->text, cancellable, NULL);
1857 struct _status_info *
1858 imapx_copy_status (struct _status_info *sinfo)
1860 struct _status_info *out;
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);
1874 imapx_free_status (struct _status_info *sinfo)
1879 switch (sinfo->condition) {
1881 g_free (sinfo->u.newname.oldname);
1882 g_free (sinfo->u.newname.newname);
1885 g_ptr_array_free (sinfo->u.copyuid.uids, FALSE);
1886 g_ptr_array_free (sinfo->u.copyuid.copied_uids, FALSE);
1888 case IMAPX_CAPABILITY:
1890 imapx_free_capability (sinfo->u.cinfo);
1896 g_free (sinfo->text);
1900 /* FIXME: use tokeniser? */
1901 /* FIXME: real flags */
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 },
1914 imapx_parse_list (CamelIMAPXStream *is,
1915 GCancellable *cancellable,
1917 /* throws io, parse */
1921 guchar *token, *p, c;
1922 struct _list_info * linfo;
1924 linfo = g_malloc0 (sizeof (*linfo));
1926 /* mailbox_list ::= "(" #("\Marked" / "\Noinferiors" /
1927 * "\Noselect" / "\Unmarked" / flag_extension) ")"
1928 * SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox */
1930 tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL);
1932 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "list: expecting '('");
1937 while ((tok = camel_imapx_stream_token (is, &token, &len, cancellable, NULL)) != ')') {
1938 if (tok == IMAPX_TOK_STRING || tok == IMAPX_TOK_TOKEN) {
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;
1946 imapx_free_list (linfo);
1947 g_set_error (error, CAMEL_IMAPX_ERROR, 1, "list: execting flag or ')'");
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);
1961 imapx_list_get_path (struct _list_info *li)
1967 if (li->separator != 0 && li->separator != '/') {
1968 p = path = alloca (strlen (li->name) * 3 + 1);
1970 while ((c = *f++ & 0xff)) {
1971 if (c == li->separator)
1973 else if (c == '/' || c == '%')
1974 p += sprintf (p, "%%%02X", c);
1982 return camel_utf7_utf8 (path);
1986 imapx_free_list (struct _list_info *linfo)
1989 g_free (linfo->name);
1994 /* ********************************************************************** */
1999 * ATOM_CHAR ::= <any CHAR except atom_specials>
2001 * atom_specials ::= "(" / ")" / "{" / SPACE / CTL / list_wildcards /
2004 * CHAR ::= <any 7 - bit US - ASCII character except NUL,
2007 * CTL ::= <any ASCII control character and DEL,
2008 * 0x00 - 0x1f, 0x7f>
2010 * SPACE ::= <ASCII SP, space, 0x20>
2012 * list_wildcards ::= "%" / "*"
2014 * quoted_specials ::= <"> / "\"
2016 * string ::= quoted / literal
2018 * literal ::= "{" number "}" CRLF *CHAR8
2019 * ;; Number represents the number of CHAR8 octets
2021 * quoted ::= <"> *QUOTED_CHAR <">
2023 * QUOTED_CHAR ::= <any TEXT_CHAR except quoted_specials> /
2024 * "\" quoted_specials
2026 * TEXT_CHAR ::= <any CHAR except CR and LF>
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,
2056 #define list_wildcards "*%"
2057 #define quoted_specials "\\\""
2058 #define atom_specials "(){" list_wildcards quoted_specials /* + CTL */
2060 /* special types for the tokeniser, come out as raw tokens */
2061 #define token_specials "\n*()[]+"
2062 #define notid_specials "\x20\r\n()[]+"
2065 imapx_utils_init (void)
2067 static gsize imapx_utils_initialized = 0;
2069 if (g_once_init_enter (&imapx_utils_initialized)) {
2073 for (i = 0; i < 128; i++) {
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;
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;
2090 imapx_specials[i] = v;
2093 create_initial_capabilities_table ();
2094 camel_imapx_set_debug_flags ();
2096 g_once_init_leave (&imapx_utils_initialized, 1);
2101 imapx_is_mask (const gchar *p)
2106 v &= imapx_specials[((guchar) * p) & 0xff];
2114 imapx_path_to_physical (const gchar *prefix,
2117 GString *out = g_string_new (prefix);
2118 const gchar *p = vpath;
2121 g_string_append_c (out, '/');
2123 while ((c = *p++)) {
2125 g_string_append (out, "/" SUBFOLDER_DIR_NAME "/");
2129 g_string_append_c (out, c);
2133 g_string_free (out, FALSE);
2139 imapx_concat (CamelIMAPXStore *imapx_store,
2140 const gchar *prefix,
2141 const gchar *suffix)
2144 gchar dir_sep = imapx_store->dir_sep;
2149 len = strlen (prefix);
2150 if (len == 0 || prefix[len - 1] == dir_sep)
2151 return g_strdup_printf ("%s%s", prefix, suffix);
2153 return g_strdup_printf ("%s%c%s", prefix, dir_sep, suffix);
2157 imapx_namespace_clear (CamelIMAPXStoreNamespace **ns)
2159 CamelIMAPXStoreNamespace *node, *next;
2162 while (node != NULL) {
2164 g_free (node->full_name);
2165 g_free (node->path);
2174 camel_imapx_namespace_list_clear (struct _CamelIMAPXNamespaceList *nsl)
2179 imapx_namespace_clear (&nsl->personal);
2180 imapx_namespace_clear (&nsl->shared);
2181 imapx_namespace_clear (&nsl->other);
2187 static CamelIMAPXStoreNamespace *
2188 imapx_namespace_copy (const CamelIMAPXStoreNamespace *ns)
2190 CamelIMAPXStoreNamespace *list, *node, *tail;
2193 tail = (CamelIMAPXStoreNamespace *) &list;
2195 while (ns != NULL) {
2196 tail->next = node = g_malloc (sizeof (CamelIMAPXStoreNamespace));
2197 node->path = g_strdup (ns->path);
2198 node->sep = ns->sep;
2208 struct _CamelIMAPXNamespaceList *
2209 camel_imapx_namespace_list_copy (const struct _CamelIMAPXNamespaceList *nsl)
2211 CamelIMAPXNamespaceList *new;
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);
2222 imapx_get_temp_uid (void)
2226 static gint counter = 0;
2227 G_LOCK_DEFINE_STATIC (lock);
2230 res = g_strdup_printf (
2232 (gulong) time (NULL),
2240 camel_imapx_destroy_job_queue_info (IMAPXJobQueueInfo *jinfo)
2242 g_hash_table_destroy (jinfo->folders);