From 8ad855fef6632e32723242fda554fce04f025036 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Wed, 17 Jan 2001 00:27:19 +0000 Subject: Delayed loading of IMAP message parts. * camel-types.h: typedef CamelMessageInfo and CamelMessageContentInfo here * camel-folder-summary.h: Add a "size" field to CamelMessageContentInfo. * camel-folder-summary.c (camel_folder_summary_content_info_new, camel_folder_summary_content_info_free): Renamed and made non-static for providers that construct their own content info. (content_info_load, content_info_save): load/save size * camel-data-wrapper.c (camel_data_wrapper_is_offline): New function to return if a data wrapper's contents are "offline". (So that, for example, we don't make thumbnails of images that haven't been loaded off the IMAP server yet.) Defaults to FALSE. * providers/imap/camel-imap-folder.c (camel_imap_folder_selected): Fix a bug in re-selecting a folder when messages have been expunged from it by another client in the meantime. (imap_get_message): Rewrite. If the message is larger than a certain size, just create a skeleton message containing CamelImapWrappers that will read parts as needed. This way, large attachments only need to be downloaded if the user looks at them, and multipart/alternative alternatives that aren't used will never be downloaded at all. (imap_update_summary): Rewrite this a bunch too to make the parsing more robust. * providers/imap/camel-imap-summary.c (CAMEL_IMAP_SUMMARY_VERSION): bump. (camel_imap_summary_new): Set build_content to TRUE. (content_info_load, content_info_save): Only save/load the content for messages that have it. (The content info gets created as a side effect of imap_get_message.) * providers/imap/camel-imap-utils.c (imap_parse_body): New routine (and helpers) to parse an IMAP 'body' FETCH response and fill in a CamelMessageContentInfo from it. * providers/imap/Makefile.am (libcamelimap_la_SOURCES, libcamelimap_la_HEADERS): add camel-imap-wrapper. svn path=/trunk/; revision=7557 --- camel/providers/imap/Makefile.am | 6 +- camel/providers/imap/camel-imap-folder.c | 468 ++++++++++++++++++++---------- camel/providers/imap/camel-imap-summary.c | 34 ++- camel/providers/imap/camel-imap-utils.c | 250 +++++++++++++++- camel/providers/imap/camel-imap-utils.h | 3 + camel/providers/imap/camel-imap-wrapper.c | 212 ++++++++++++++ camel/providers/imap/camel-imap-wrapper.h | 68 +++++ 7 files changed, 884 insertions(+), 157 deletions(-) create mode 100644 camel/providers/imap/camel-imap-wrapper.c create mode 100644 camel/providers/imap/camel-imap-wrapper.h (limited to 'camel/providers') diff --git a/camel/providers/imap/Makefile.am b/camel/providers/imap/Makefile.am index aa34eac652..341fa6b421 100644 --- a/camel/providers/imap/Makefile.am +++ b/camel/providers/imap/Makefile.am @@ -28,7 +28,8 @@ libcamelimap_la_SOURCES = \ camel-imap-search.c \ camel-imap-store.c \ camel-imap-summary.c \ - camel-imap-utils.c + camel-imap-utils.c \ + camel-imap-wrapper.c libcamelimapinclude_HEADERS = \ camel-imap-auth.h \ @@ -38,7 +39,8 @@ libcamelimapinclude_HEADERS = \ camel-imap-store.h \ camel-imap-summary.h \ camel-imap-types.h \ - camel-imap-utils.h + camel-imap-utils.h \ + camel-imap-wrapper.h libcamelimap_la_LDFLAGS = $(KRB4_LDFLAGS) -version-info 0:0:0 diff --git a/camel/providers/imap/camel-imap-folder.c b/camel/providers/imap/camel-imap-folder.c index 373ded8bde..1c8d793074 100644 --- a/camel/providers/imap/camel-imap-folder.c +++ b/camel/providers/imap/camel-imap-folder.c @@ -43,6 +43,7 @@ #include "camel-imap-store.h" #include "camel-imap-summary.h" #include "camel-imap-utils.h" +#include "camel-imap-wrapper.h" #include "string-utils.h" #include "camel-stream.h" #include "camel-stream-fs.h" @@ -56,6 +57,7 @@ #include "camel-exception.h" #include "camel-mime-utils.h" #include "camel-imap-private.h" +#include "camel-multipart.h" #define d(x) x @@ -235,7 +237,7 @@ camel_imap_folder_selected (CamelFolder *folder, CamelImapResponse *response, /* If we've lost messages, we have to rescan everything */ if (exists < count) { - imap_rescan (folder, count, ex); + imap_rescan (folder, exists, ex); return; } @@ -596,87 +598,298 @@ imap_move_message_to (CamelFolder *source, const char *uid, camel_folder_delete_message (source, uid); } -static CamelMimeMessage * -imap_get_message (CamelFolder *folder, const gchar *uid, CamelException *ex) +static GPtrArray * +imap_search_by_expression (CamelFolder *folder, const char *expression, CamelException *ex) +{ + CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); + GPtrArray *matches, *summary; + + /* we could get around this by creating a new search object each time, + but i doubt its worth it since any long operation would lock the + command channel too */ + CAMEL_IMAP_FOLDER_LOCK(folder, search_lock); + + if (!imap_folder->search) + imap_folder->search = camel_imap_search_new (); + + camel_folder_search_set_folder (imap_folder->search, folder); + summary = camel_folder_get_summary(folder); + camel_folder_search_set_summary(imap_folder->search, summary); + matches = camel_folder_search_execute_expression (imap_folder->search, expression, ex); + + CAMEL_IMAP_FOLDER_UNLOCK(folder, search_lock); + + camel_folder_free_summary(folder, summary); + + return matches; +} + +static void +imap_search_free (CamelFolder *folder, GPtrArray *uids) +{ + CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); + + g_return_if_fail (imap_folder->search); + + CAMEL_IMAP_FOLDER_LOCK(folder, search_lock); + + camel_folder_search_free_result (imap_folder->search, uids); + + CAMEL_IMAP_FOLDER_UNLOCK(folder, search_lock); +} + +/* parse a header response (starting after the first ' ' after + * *@headers_p) and construct a content-free CamelMedium from it. + */ +static CamelMedium * +parse_headers (char **headers_p, CamelType medium_type) +{ + CamelMedium *medium; + CamelStream *stream; + char *headers; + int len; + + *headers_p = strchr (*headers_p, ' '); + if (!*headers_p) + return FALSE; + (*headers_p)++; + + headers = imap_parse_nstring (headers_p, &len); + if (!headers) + return FALSE; + stream = camel_stream_mem_new_with_buffer (headers, len); + g_free (headers); + + medium = CAMEL_MEDIUM (camel_object_new (medium_type)); + camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (medium), stream); + camel_object_unref (CAMEL_OBJECT (stream)); + + return medium; +} + +static CamelMedium * +fetch_medium (CamelFolder *folder, const char *uid, const char *section_text, + CamelType type, CamelException *ex) { CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); CamelImapResponse *response; - CamelStream *msgstream; - CamelMimeMessage *msg; - char *result, *mesg, *p; - int len; + CamelMedium *medium; + char *result, *p; - CAMEL_IMAP_STORE_LOCK(store, command_lock); + CAMEL_IMAP_STORE_LOCK (store, command_lock); response = camel_imap_command (store, folder, ex, - "UID FETCH %s BODY.PEEK[]", uid); - CAMEL_IMAP_STORE_UNLOCK(store, command_lock); - + "UID FETCH %s BODY.PEEK[%s]", + uid, section_text); + CAMEL_IMAP_STORE_UNLOCK (store, command_lock); if (!response) return NULL; + + /* FIXME: there could be multiple lines of FETCH response. */ result = camel_imap_response_extract (response, "FETCH", ex); if (!result) return NULL; - p = strstr (result, "BODY[]"); - if (p) { - p += 7; - mesg = imap_parse_nstring (&p, &len); - } - if (!p) { + p = e_strstrcase (result, "BODY"); + if (p) + medium = parse_headers (&p, type); + else { camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, _("Could not find message body in FETCH " "response.")); - g_free (result); - return NULL; + medium = NULL; } g_free (result); - msgstream = camel_stream_mem_new_with_buffer (mesg, len); - msg = camel_mime_message_new (); - camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg), - msgstream); - camel_object_unref (CAMEL_OBJECT (msgstream)); - g_free (mesg); + return medium; +} + +static CamelMimeMessage *get_message (CamelFolder *folder, const char *uid, + const char *part_specifier, + CamelMessageContentInfo *ci, + CamelException *ex); + +/* Fetch the contents of the MIME part indicated by @ci, which is part + * of message @uid in @folder. + */ +static CamelDataWrapper * +get_content (CamelFolder *folder, const char *uid, const char *part_spec, + CamelMimePart *part, CamelMessageContentInfo *ci, + CamelException *ex) +{ + char *child_spec; + + /* There are three cases: multipart, message/rfc822, and "other" */ + + if (header_content_type_is (ci->type, "multipart", "*")) { + CamelMultipart *body_mp; + CamelDataWrapper *content; + int speclen, num; + + body_mp = camel_multipart_new (); + camel_data_wrapper_set_mime_type_field ( + CAMEL_DATA_WRAPPER (body_mp), ci->type); + camel_multipart_set_boundary (body_mp, NULL); + + speclen = strlen (part_spec); + child_spec = g_malloc (speclen + 15); + memcpy (child_spec, part_spec, speclen); + if (speclen > 0) + child_spec[speclen++] = '.'; + + ci = ci->childs; + num = 1; + while (ci) { + sprintf (child_spec + speclen, "%d.MIME", num++); + part = (CamelMimePart *)fetch_medium (folder, uid, child_spec, CAMEL_MIME_PART_TYPE, ex); + *(strchr (child_spec + speclen, '.')) = '\0'; + if (part) + content = get_content (folder, uid, child_spec, part, ci, ex); + if (!part || !content) { + g_free (child_spec); + camel_object_unref (CAMEL_OBJECT (part)); + camel_object_unref (CAMEL_OBJECT (body_mp)); + return NULL; + } + camel_medium_set_content_object (CAMEL_MEDIUM (part), + content); + camel_object_unref (CAMEL_OBJECT (content)); + camel_multipart_add_part (body_mp, part); + camel_object_unref (CAMEL_OBJECT (part)); + + ci = ci->next; + } + g_free (child_spec); + + return (CamelDataWrapper *)body_mp; + } else if (header_content_type_is (ci->type, "message", "rfc822")) { + return (CamelDataWrapper *) + get_message (folder, uid, part_spec, ci->childs, ex); + } else { + CamelDataWrapper *content; + + if (!ci->parent || header_content_type_is (ci->parent->type, "message", "rfc822")) + child_spec = g_strdup_printf ("%s%s1", part_spec, *part_spec ? "." : ""); + else + child_spec = g_strdup (part_spec); + content = camel_imap_wrapper_new (folder, ci->type, uid, child_spec, part); + g_free (child_spec); + return content; + } +} + +static CamelMimeMessage * +get_message (CamelFolder *folder, const char *uid, const char *part_spec, + CamelMessageContentInfo *ci, CamelException *ex) +{ + CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); + CamelDataWrapper *content; + CamelMimeMessage *msg; + char *section_text; + + section_text = g_strdup_printf ("%s%s%s", part_spec, *part_spec ? "." : "", + store->server_level >= IMAP_LEVEL_IMAP4REV1 ? "HEADER" : "0"); + msg = (CamelMimeMessage *)fetch_medium (folder, uid, section_text, CAMEL_MIME_MESSAGE_TYPE, ex); + g_free (section_text); + if (!msg) + return NULL; + + content = get_content (folder, uid, part_spec, CAMEL_MIME_PART (msg), ci, ex); + if (!content) { + camel_object_unref (CAMEL_OBJECT (msg)); + return NULL; + } + + camel_medium_set_content_object (CAMEL_MEDIUM (msg), content); + camel_object_unref (CAMEL_OBJECT (content)); return msg; } -/** - * imap_protocol_get_summary_specifier - * - * Make a data item specifier for the header lines we need, - * appropriate to the server level. - **/ -static char * -imap_protocol_get_summary_specifier (CamelImapStore *store) +/* FIXME: I pulled this number out of my butt. */ +#define IMAP_SMALL_BODY_SIZE 5120 + +static CamelMimeMessage * +imap_get_message (CamelFolder *folder, const char *uid, CamelException *ex) { - char *sect_begin, *sect_end; - char *headers_wanted = "SUBJECT FROM TO CC DATE MESSAGE-ID REFERENCES IN-REPLY-TO"; + CamelMessageInfo *mi; + CamelMimeMessage *msg; - if (store->server_level >= IMAP_LEVEL_IMAP4REV1) { - sect_begin = "BODY.PEEK[HEADER.FIELDS"; - sect_end = "]"; - } else { - sect_begin = "RFC822.HEADER.LINES"; - sect_end = ""; + mi = camel_folder_summary_uid (folder->summary, uid); + g_return_val_if_fail (mi != NULL, NULL); + + /* Fetch small messages directly. */ + if (mi->size < IMAP_SMALL_BODY_SIZE) { + camel_folder_summary_info_free (folder->summary, mi); + return (CamelMimeMessage *)fetch_medium (folder, uid, "", CAMEL_MIME_MESSAGE_TYPE, ex); } - return g_strdup_printf ("UID FLAGS RFC822.SIZE %s (%s)%s", sect_begin, - headers_wanted, sect_end); + /* For larger messages, fetch the structure and build a message + * with offline parts. (We check mi->content->type rather than + * mi->content because camel_folder_summary_info_new always creates + * an empty content struct.) + */ + if (!mi->content->type) { + CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); + CamelImapResponse *response; + char *result, *p; + + CAMEL_IMAP_STORE_LOCK (store, command_lock); + response = camel_imap_command (store, folder, ex, + "UID FETCH %s BODY", uid); + CAMEL_IMAP_STORE_UNLOCK (store, command_lock); + if (!response) { + camel_folder_summary_info_free (folder->summary, mi); + return NULL; + } + /* FIXME, wrong */ + result = camel_imap_response_extract (response, "FETCH", ex); + if (!result) { + camel_folder_summary_info_free (folder->summary, mi); + return NULL; + } + + p = e_strstrcase (result, "BODY "); + if (p) { + p += 5; + imap_parse_body (&p, folder, mi->content); + } + g_free (result); + if (!mi->content->type) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, + _("Could not find message body in FETCH response.")); + camel_folder_summary_info_free (folder->summary, mi); + return NULL; + } + } + + msg = get_message (folder, uid, "", mi->content, ex); + camel_folder_summary_info_free (folder->summary, mi); + + return msg; +} + +static const char * +imap_protocol_get_summary_specifier (CamelImapStore *store) +{ + if (store->server_level >= IMAP_LEVEL_IMAP4REV1) + return "UID FLAGS RFC822.SIZE BODY.PEEK[HEADER]"; + else + return "UID FLAGS RFC822.SIZE RFC822.HEADER"; } -/* Called with the store's command_lock locked */ static void imap_update_summary (CamelFolder *folder, int first, int last, CamelFolderChangeInfo *changes, CamelException *ex) { CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); - /*CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);*/ CamelImapResponse *response; - GPtrArray *headers = NULL; - char *q, *summary_specifier; - struct _header_raw *h = NULL; - int i; + GPtrArray *headers, *messages; + const char *summary_specifier; + char *p, *uid; + int i, seq, count; + CamelMimeMessage *msg; + CamelMessageInfo *mi; + guint32 flags, size; summary_specifier = imap_protocol_get_summary_specifier (store); /* We already have the command lock */ @@ -689,118 +902,79 @@ imap_update_summary (CamelFolder *folder, int first, int last, "FETCH %d:%d (%s)", first, last, summary_specifier); } - g_free (summary_specifier); if (!response) return; + count = camel_folder_summary_count (folder->summary); + messages = g_ptr_array_new (); + g_ptr_array_set_size (messages, last - first + 1); headers = response->untagged; for (i = 0; i < headers->len; i++) { - CamelMessageInfo *info; - CamelImapMessageInfo *iinfo; - char *uid, *flags, *header, *size; - - /* Grab the UID... */ - if (!(uid = strstr (headers->pdata[i], "UID "))) { - d(fprintf (stderr, "Cannot get a uid for %d\n\n%s\n\n", i+1, (char *) headers->pdata[i])); - break; + p = headers->pdata[i]; + if (*p++ != '*' || *p++ != ' ') + continue; + seq = strtoul (p, &p, 10); + if (!seq || seq < count) + continue; + if (g_strncasecmp (p, " FETCH (", 8) != 0) + continue; + p += 8; + + mi = messages->pdata[seq - first]; + flags = size = 0; + uid = NULL; + while (p && *p != ')') { + if (*p == ' ') + p++; + if (!g_strncasecmp (p, "FLAGS ", 6)) { + p += 6; + /* FIXME user flags */ + flags = imap_parse_flag_list (&p); + } else if (!g_strncasecmp (p, "RFC822.SIZE ", 12)) { + size = strtoul (p + 12, &p, 10); + } else if (!g_strncasecmp (p, "UID ", 4)) { + uid = p + 4; + strtoul (uid, &p, 10); + uid = g_strndup (uid, p - uid); + } else if (!g_strncasecmp (p, "BODY[HEADER", 11) || + !g_strncasecmp (p, "RFC822.HEADER", 13)) { + msg = (CamelMimeMessage *) parse_headers (&p, CAMEL_MIME_MESSAGE_TYPE); + mi = camel_folder_summary_info_new_from_message (folder->summary, msg); + camel_object_unref (CAMEL_OBJECT (msg)); + } else { + g_warning ("Waiter, I did not order this %.*s", + (int)strcspn (p, " \n"), p); + p = NULL; + } } - for (uid += 4; *uid && (*uid < '0' || *uid > '9'); uid++) - ; - for (q = uid; *q && *q >= '0' && *q <= '9'; q++) - ; - - /* construct the header list */ - /* fast-forward to beginning of header info... */ - header = strchr (headers->pdata[i], '\n') + 1; - h = NULL; - do { - char *line; - int len; - - len = strcspn (header, "\n"); - while (header[len + 1] == ' ' || - header[len + 1] == '\t') - len += 1 + strcspn (header + len + 1, "\n"); - line = g_strndup (header, len); - header_raw_append_parse (&h, line, -1); - g_free (line); - - header += len; - } while (*header++ == '\n' && *header != '\n'); - - /* We can't just call camel_folder_summary_add_from_parser - * because it will assign the wrong UID, and thus get the - * uid hash table wrong and all that. FIXME some day. - * Well you can actually now, because you can override next_uid_string(), but - * it hasn't been done yet. + /* Ideally we got everything on one line, but if we + * we didn't, and we didn't get the body yet, then we + * have to postpone this line for later. */ - info = camel_folder_summary_info_new_from_header(folder->summary, h); - iinfo = (CamelImapMessageInfo *)info; - header_raw_clear (&h); - uid = g_strndup (uid, q - uid); - camel_folder_change_info_add_uid (changes, uid); - camel_message_info_set_uid (info, uid); - - /* now lets grab the FLAGS */ - if (!(flags = strstr (headers->pdata[i], "FLAGS "))) { - d(fprintf (stderr, "We didn't seem to get any flags for %d...\n", i)); - } else { - flags += 6; - info->flags = imap_parse_flag_list (&flags); - iinfo->server_flags = info->flags; + if (mi == NULL) { + p = headers->pdata[i]; + g_ptr_array_remove_index (headers, i); + g_ptr_array_add (headers, p); + continue; } - /* And size */ - if (!(size = strstr (headers->pdata[i], "RFC822.SIZE "))) { - d(fprintf (stderr, "We didn't seem to get any size for %d...\n", i)); - } else - info->size = strtoul (size + 12, NULL, 10); - - camel_folder_summary_add (folder->summary, info); + messages->pdata[seq - first] = mi; + if (uid) + camel_message_info_set_uid (mi, uid); + if (flags) + mi->flags = flags; + if (size) + mi->size = size; } camel_imap_response_free (response); -} - -static GPtrArray * -imap_search_by_expression (CamelFolder *folder, const char *expression, CamelException *ex) -{ - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); - GPtrArray *matches, *summary; - - /* we could get around this by creating a new search object each time, - but i doubt its worth it since any long operation would lock the - command channel too */ - CAMEL_IMAP_FOLDER_LOCK(folder, search_lock); - - if (!imap_folder->search) - imap_folder->search = camel_imap_search_new (); - - camel_folder_search_set_folder (imap_folder->search, folder); - summary = camel_folder_get_summary(folder); - camel_folder_search_set_summary(imap_folder->search, summary); - matches = camel_folder_search_execute_expression (imap_folder->search, expression, ex); - CAMEL_IMAP_FOLDER_UNLOCK(folder, search_lock); - - camel_folder_free_summary(folder, summary); - - return matches; -} - -static void -imap_search_free (CamelFolder *folder, GPtrArray *uids) -{ - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); - - g_return_if_fail (imap_folder->search); - - CAMEL_IMAP_FOLDER_LOCK(folder, search_lock); - - camel_folder_search_free_result (imap_folder->search, uids); - - CAMEL_IMAP_FOLDER_UNLOCK(folder, search_lock); + for (i = 0; i < messages->len; i++) { + mi = messages->pdata[i]; + camel_folder_summary_add (folder->summary, mi); + } + g_ptr_array_free (messages, TRUE); } /* Called with the store's command_lock locked */ diff --git a/camel/providers/imap/camel-imap-summary.c b/camel/providers/imap/camel-imap-summary.c index 5d657b6d68..dd77673cad 100644 --- a/camel/providers/imap/camel-imap-summary.c +++ b/camel/providers/imap/camel-imap-summary.c @@ -32,14 +32,17 @@ #include #include -#define CAMEL_IMAP_SUMMARY_VERSION (0x2000) +#define CAMEL_IMAP_SUMMARY_VERSION (0x300) static int summary_header_load (CamelFolderSummary *, FILE *); static int summary_header_save (CamelFolderSummary *, FILE *); static CamelMessageInfo *message_info_load (CamelFolderSummary *s, FILE *in); -static int message_info_save (CamelFolderSummary *s, FILE *out, - CamelMessageInfo *info); +static int message_info_save (CamelFolderSummary *s, FILE *out, + CamelMessageInfo *info); +static CamelMessageContentInfo *content_info_load (CamelFolderSummary *s, FILE *in); +static int content_info_save (CamelFolderSummary *s, FILE *out, + CamelMessageContentInfo *info); static void camel_imap_summary_class_init (CamelImapSummaryClass *klass); static void camel_imap_summary_init (CamelImapSummary *obj); @@ -76,6 +79,8 @@ camel_imap_summary_class_init (CamelImapSummaryClass *klass) cfs_class->summary_header_save = summary_header_save; cfs_class->message_info_load = message_info_load; cfs_class->message_info_save = message_info_save; + cfs_class->content_info_load = content_info_load; + cfs_class->content_info_save = content_info_save; } static void @@ -109,7 +114,7 @@ camel_imap_summary_new (const char *filename, guint32 validity) camel_object_new (camel_imap_summary_get_type ())); CamelImapSummary *imap_summary = (CamelImapSummary *)summary; - camel_folder_summary_set_build_content (summary, FALSE); + camel_folder_summary_set_build_content (summary, TRUE); camel_folder_summary_set_filename (summary, filename); if (camel_folder_summary_load (summary) == -1) { @@ -186,3 +191,24 @@ message_info_save (CamelFolderSummary *s, FILE *out, CamelMessageInfo *info) return camel_folder_summary_encode_uint32 (out, iinfo->server_flags); } + + +static CamelMessageContentInfo * +content_info_load (CamelFolderSummary *s, FILE *in) +{ + if (fgetc (in)) + return camel_imap_summary_parent->content_info_load (s, in); + else + return camel_folder_summary_content_info_new (s); +} + +static int +content_info_save (CamelFolderSummary *s, FILE *out, + CamelMessageContentInfo *info) +{ + if (info->type) { + fputc (1, out); + return camel_imap_summary_parent->content_info_save (s, out, info); + } else + return fputc (0, out); +} diff --git a/camel/providers/imap/camel-imap-utils.c b/camel/providers/imap/camel-imap-utils.c index 1b4aaba499..8116f12386 100644 --- a/camel/providers/imap/camel-imap-utils.c +++ b/camel/providers/imap/camel-imap-utils.c @@ -25,11 +25,9 @@ #include #include -#include #include "camel-imap-utils.h" -#include "string-utils.h" -#include -#include "camel/camel-folder-summary.h" +#include "camel-imap-summary.h" +#include "camel-folder.h" #define d(x) x @@ -290,6 +288,250 @@ imap_parse_string_generic (char **str_p, int *len, int type) } } +static inline void +skip_char (char **str_p, char ch) +{ + if (*str_p && **str_p == ch) + *str_p = *str_p + 1; + else + *str_p = NULL; +} + +/* Skip atom, string, or number */ +static void +skip_asn (char **str_p) +{ + char *str = *str_p; + + if (!str) + return; + else if (*str == '"') { + while (*++str && *str != '"') { + if (*str == '\\') { + str++; + if (!*str) + break; + } + } + if (*str == '"') + *str_p = str + 1; + else + *str_p = NULL; + } else if (*str == '{') { + unsigned long len; + + len = strtoul (str + 1, &str, 10); + if (*str != '}' || *(str + 1) != '\n' || + strlen (str + 2) < len) { + *str_p = NULL; + return; + } + *str_p = str + 2 + len; + } else { + /* We assume the string is well-formed and don't + * bother making sure it's a valid atom. + */ + while (*str && *str != ')' && *str != ' ') + str++; + *str_p = str; + } +} + +static void +skip_list (char **str_p) +{ + skip_char (str_p, '('); + while (*str_p && **str_p != ')') { + if (**str_p == '(') + skip_list (str_p); + else + skip_asn (str_p); + if (*str_p && **str_p == ' ') + skip_char (str_p, ' '); + } + skip_char (str_p, ')'); +} + +static void +parse_params (char **parms_p, CamelContentType *type) +{ + char *parms = *parms_p, *name, *value; + int len; + + if (!g_strncasecmp (parms, "nil", 3)) { + *parms_p += 3; + return; + } + + if (*parms++ != '(') { + *parms_p = NULL; + return; + } + + while (parms && *parms != ')') { + name = imap_parse_nstring (&parms, &len); + skip_char (&parms, ' '); + value = imap_parse_nstring (&parms, &len); + + if (name && value) + header_content_type_set_param (type, name, value); + g_free (name); + g_free (value); + + if (parms && *parms == ' ') + parms++; + } + + if (!parms || *parms++ != ')') { + *parms_p = NULL; + return; + } + *parms_p = parms; +} + +/** + * imap_parse_body: + * @body_p: pointer to the start of an IMAP "body" + * @folder: an imap folder + * @ci: a CamelMessageContentInfo to fill in + * + * This filles in @ci with data from *@body_p. On success *@body_p + * will point to the character after the body. On failure, it will be + * set to %NULL and @ci will be unchanged. + **/ +void +imap_parse_body (char **body_p, CamelFolder *folder, + CamelMessageContentInfo *ci) +{ + char *body = *body_p; + CamelMessageContentInfo *child; + CamelContentType *type; + int len; + + if (*body++ != '(') { + *body_p = NULL; + return; + } + + if (*body == '(') { + /* multipart */ + GPtrArray *children; + char *subtype; + int i; + + /* Parse the child body parts */ + children = g_ptr_array_new (); + i = 0; + while (body && *body == '(') { + child = camel_folder_summary_content_info_new (folder->summary); + g_ptr_array_add (children, child); + imap_parse_body (&body, folder, child); + if (!body) + break; + child->parent = ci; + } + skip_char (&body, ' '); + + /* Parse the multipart subtype */ + subtype = imap_parse_string (&body, &len); + + /* If there is a parse error, abort. */ + if (!body) { + for (i = 0; i < children->len; i++) { + child = children->pdata[i]; + camel_folder_summary_content_info_free (folder->summary, child); + } + g_ptr_array_free (children, TRUE); + *body_p = NULL; + return; + } + + g_strdown (subtype); + ci->type = header_content_type_new ("multipart", subtype); + g_free (subtype); + + /* Chain the children. */ + ci->childs = children->pdata[0]; + ci->size = 0; + for (i = 0; i < children->len - 1; i++) { + child = children->pdata[i]; + child->next = children->pdata[i + 1]; + ci->size += child->size; + } + g_ptr_array_free (children, TRUE); + } else { + /* single part */ + char *main_type, *subtype; + char *id, *description, *encoding; + guint32 size; + + main_type = imap_parse_string (&body, &len); + skip_char (&body, ' '); + subtype = imap_parse_string (&body, &len); + skip_char (&body, ' '); + if (!body) { + g_free (main_type); + g_free (subtype); + *body_p = NULL; + return; + } + g_strdown (main_type); + g_strdown (subtype); + type = header_content_type_new (main_type, subtype); + g_free (main_type); + g_free (subtype); + parse_params (&body, type); + skip_char (&body, ' '); + + id = imap_parse_nstring (&body, &len); + skip_char (&body, ' '); + description = imap_parse_nstring (&body, &len); + skip_char (&body, ' '); + encoding = imap_parse_string (&body, &len); + skip_char (&body, ' '); + if (body) + size = strtoul (body, &body, 10); + + child = NULL; + if (header_content_type_is (type, "message", "rfc822")) { + skip_char (&body, ' '); + skip_list (&body); /* envelope */ + skip_char (&body, ' '); + child = camel_folder_summary_content_info_new (folder->summary); + imap_parse_body (&body, folder, child); + if (!body) + camel_folder_summary_content_info_free (folder->summary, child); + skip_char (&body, ' '); + if (body) + strtoul (body, &body, 10); + } else if (header_content_type_is (type, "text", "*")) { + if (body) + strtoul (body, &body, 10); + } + + if (body) { + ci->type = type; + ci->id = id; + ci->description = description; + ci->encoding = encoding; + ci->size = size; + ci->childs = child; + } else { + header_content_type_unref (type); + g_free (id); + g_free (description); + g_free (encoding); + } + } + + if (!body || *body++ != ')') { + *body_p = NULL; + return; + } + + *body_p = body; +} + /** * imap_quote_string: * @str: the string to quote, which must not contain CR or LF diff --git a/camel/providers/imap/camel-imap-utils.h b/camel/providers/imap/camel-imap-utils.h index d6df958ac9..985f177b3a 100644 --- a/camel/providers/imap/camel-imap-utils.h +++ b/camel/providers/imap/camel-imap-utils.h @@ -50,6 +50,9 @@ char *imap_parse_string_generic (char **str_p, int *len, int type); #define imap_parse_astring(str_p, len_p) \ imap_parse_string_generic (str_p, len_p, IMAP_ASTRING) +void imap_parse_body (char **body_p, CamelFolder *folder, + CamelMessageContentInfo *ci); + char *imap_quote_string (const char *str); #ifdef __cplusplus diff --git a/camel/providers/imap/camel-imap-wrapper.c b/camel/providers/imap/camel-imap-wrapper.c new file mode 100644 index 0000000000..2277b7747f --- /dev/null +++ b/camel/providers/imap/camel-imap-wrapper.c @@ -0,0 +1,212 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; -*- */ +/* camel-imap-wrapper.c: data wrapper for offline IMAP data */ + +/* + * Author: Dan Winship + * + * Copyright 2000 Helix Code, Inc. (http://www.helixcode.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include + +#include "camel-imap-wrapper.h" +#include "camel-imap-command.h" +#include "camel-imap-store.h" +#include "camel-imap-utils.h" +#include "camel-imap-private.h" +#include "camel-exception.h" +#include "camel-folder.h" +#include "camel-stream-mem.h" +#include "camel-stream-filter.h" +#include "camel-mime-filter-basic.h" +#include "camel-mime-filter-crlf.h" +#include "camel-mime-filter-charset.h" +#include "camel-mime-part.h" + +#include +#include + +static CamelDataWrapperClass *parent_class = NULL; + +/* Returns the class for a CamelDataWrapper */ +#define CDW_CLASS(so) CAMEL_DATA_WRAPPER_CLASS (CAMEL_OBJECT_GET_CLASS(so)) + +static int write_to_stream (CamelDataWrapper *imap_wrapper, CamelStream *stream); + +static void +camel_imap_wrapper_class_init (CamelImapWrapperClass *camel_imap_wrapper_class) +{ + CamelDataWrapperClass *camel_data_wrapper_class = + CAMEL_DATA_WRAPPER_CLASS (camel_imap_wrapper_class); + + parent_class = CAMEL_DATA_WRAPPER_CLASS (camel_type_get_global_classfuncs (camel_data_wrapper_get_type ())); + + /* virtual method override */ + camel_data_wrapper_class->write_to_stream = write_to_stream; +} + +static void +camel_imap_wrapper_finalize (CamelObject *object) +{ + CamelImapWrapper *imap_wrapper = CAMEL_IMAP_WRAPPER (object); + + if (imap_wrapper->folder) + camel_object_unref (CAMEL_OBJECT (imap_wrapper->folder)); + if (imap_wrapper->uid) + g_free (imap_wrapper->uid); + if (imap_wrapper->part) + g_free (imap_wrapper->part_spec); +} + +CamelType +camel_imap_wrapper_get_type (void) +{ + static CamelType camel_imap_wrapper_type = CAMEL_INVALID_TYPE; + + if (camel_imap_wrapper_type == CAMEL_INVALID_TYPE) { + camel_imap_wrapper_type = camel_type_register ( + CAMEL_DATA_WRAPPER_TYPE, "CamelImapWrapper", + sizeof (CamelImapWrapper), + sizeof (CamelImapWrapperClass), + (CamelObjectClassInitFunc) camel_imap_wrapper_class_init, + NULL, + NULL, + (CamelObjectFinalizeFunc) camel_imap_wrapper_finalize); + } + + return camel_imap_wrapper_type; +} + + +static int +write_to_stream (CamelDataWrapper *data_wrapper, CamelStream *stream) +{ + CamelImapWrapper *imap_wrapper = CAMEL_IMAP_WRAPPER (data_wrapper); + CamelImapStore *store; + CamelImapResponse *response; + CamelStream *memstream; + CamelStreamFilter *filterstream; + CamelMimeFilter *filter; + CamelContentType *ct; + char *result, *p, *body; + int len; + + if (!data_wrapper->offline) + return parent_class->write_to_stream (data_wrapper, stream); + + store = CAMEL_IMAP_STORE (imap_wrapper->folder->parent_store); + CAMEL_IMAP_STORE_LOCK (store, command_lock); + response = camel_imap_command (store, imap_wrapper->folder, NULL, + "UID FETCH %s BODY.PEEK[%s]", + imap_wrapper->uid, + imap_wrapper->part_spec); + CAMEL_IMAP_STORE_UNLOCK (store, command_lock); + if (!response) + goto lose; + + result = camel_imap_response_extract (response, "FETCH", NULL); + if (!result) + goto lose; + + p = strchr (result, ']'); + if (!p) { + g_free (result); + goto lose; + } + p += 2; + + body = imap_parse_nstring (&p, &len); + g_free (result); + if (!body) + goto lose; + + memstream = camel_stream_mem_new_with_buffer (body, len); + g_free (body); + filterstream = camel_stream_filter_new_with_stream (memstream); + + if (camel_mime_part_get_encoding (imap_wrapper->part) == + CAMEL_MIME_PART_ENCODING_BASE64) { + filter = (CamelMimeFilter *)camel_mime_filter_basic_new_type (CAMEL_MIME_FILTER_BASIC_BASE64_DEC); + camel_stream_filter_add (filterstream, filter); + } else if (camel_mime_part_get_encoding (imap_wrapper->part) == + CAMEL_MIME_PART_ENCODING_QUOTEDPRINTABLE) { + filter = (CamelMimeFilter *)camel_mime_filter_basic_new_type (CAMEL_MIME_FILTER_BASIC_QP_DEC); + camel_stream_filter_add (filterstream, filter); + } else + filter = NULL; + + ct = camel_mime_part_get_content_type (imap_wrapper->part); + if (header_content_type_is (ct, "text", "*")) { + const char *charset; + + /* If we just did B64/QP, need to also do CRLF->LF */ + if (filter) { + filter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_DECODE, + CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY); + camel_stream_filter_add (filterstream, filter); + } + + charset = header_content_type_param (ct, "charset"); + if (charset && !(strcasecmp (charset, "us-ascii") == 0 + || strcasecmp (charset, "utf-8") == 0)) { + filter = (CamelMimeFilter *)camel_mime_filter_charset_new_convert (charset, "UTF-8"); + if (filter) + camel_stream_filter_add (filterstream, filter); + } + } + + data_wrapper->stream = CAMEL_STREAM (filterstream); + data_wrapper->offline = FALSE; + + camel_object_unref (CAMEL_OBJECT (imap_wrapper->folder)); + imap_wrapper->folder = NULL; + g_free (imap_wrapper->uid); + imap_wrapper->uid = NULL; + g_free (imap_wrapper->part_spec); + imap_wrapper->part = NULL; + + return parent_class->write_to_stream (data_wrapper, stream); + + lose: + errno = ENETUNREACH; + return -1; +} + + +CamelDataWrapper * +camel_imap_wrapper_new (CamelFolder *folder, CamelContentType *type, + const char *uid, const char *part_spec, + CamelMimePart *part) +{ + CamelImapWrapper *imap_wrapper; + + imap_wrapper = (CamelImapWrapper *)camel_object_new(camel_imap_wrapper_get_type()); + + camel_data_wrapper_set_mime_type_field (CAMEL_DATA_WRAPPER (imap_wrapper), type); + ((CamelDataWrapper *)imap_wrapper)->offline = TRUE; + + imap_wrapper->folder = folder; + camel_object_ref (CAMEL_OBJECT (folder)); + imap_wrapper->uid = g_strdup (uid); + imap_wrapper->part_spec = g_strdup (part_spec); + + /* Don't ref this, it's our parent. */ + imap_wrapper->part = part; + + return (CamelDataWrapper *)imap_wrapper; +} diff --git a/camel/providers/imap/camel-imap-wrapper.h b/camel/providers/imap/camel-imap-wrapper.h new file mode 100644 index 0000000000..34b28424b7 --- /dev/null +++ b/camel/providers/imap/camel-imap-wrapper.h @@ -0,0 +1,68 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* camel-imap-wrapper.h: data wrapper for offline IMAP data */ + +/* + * Copyright 2000 Helix Code, Inc. (http://www.helixcode.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + + +#ifndef CAMEL_IMAP_WRAPPER_H +#define CAMEL_IMAP_WRAPPER_H 1 + + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus }*/ + +#include + +#define CAMEL_IMAP_WRAPPER_TYPE (camel_imap_wrapper_get_type ()) +#define CAMEL_IMAP_WRAPPER(obj) (CAMEL_CHECK_CAST((obj), CAMEL_IMAP_WRAPPER_TYPE, CamelImapWrapper)) +#define CAMEL_IMAP_WRAPPER_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_IMAP_WRAPPER_TYPE, CamelImapWrapperClass)) +#define CAMEL_IS_IMAP_WRAPPER(o) (CAMEL_CHECK_TYPE((o), CAMEL_IMAP_WRAPPER_TYPE)) + +typedef struct +{ + CamelDataWrapper parent_object; + + CamelFolder *folder; + char *uid, *part_spec; + CamelMimePart *part; +} CamelImapWrapper; + +typedef struct { + CamelDataWrapperClass parent_class; + +} CamelImapWrapperClass; + +/* Standard Camel function */ +CamelType camel_imap_wrapper_get_type (void); + +/* Constructor */ +CamelDataWrapper *camel_imap_wrapper_new (CamelFolder *folder, + CamelContentType *type, + const char *uid, + const char *part_spec, + CamelMimePart *part); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CAMEL_DATA_WRAPPER_H */ -- cgit v1.2.3