aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--camel/ChangeLog5
-rw-r--r--camel/providers/imap/camel-imap-fetch.c509
2 files changed, 514 insertions, 0 deletions
diff --git a/camel/ChangeLog b/camel/ChangeLog
index 9fe3cea230..22f5fb3e7a 100644
--- a/camel/ChangeLog
+++ b/camel/ChangeLog
@@ -1,3 +1,8 @@
+2001-01-04 Dan Winship <danw@helixcode.com>
+
+ * providers/imap/camel-imap-folder.c (imap_rescan): Fix two
+ problems in figuring out server-expunged messages.
+
2001-01-04 Not Zed <NotZed@HelixCode.com>
* camel-folder.c (thaw): If we have a lot of messages changed,
diff --git a/camel/providers/imap/camel-imap-fetch.c b/camel/providers/imap/camel-imap-fetch.c
new file mode 100644
index 0000000000..5d703e61dc
--- /dev/null
+++ b/camel/providers/imap/camel-imap-fetch.c
@@ -0,0 +1,509 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Dan Winship <danw@helixcode.com>
+ *
+ * Copyright 2000 Helix Code, Inc. (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 Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <ctype.h>
+#include <string.h>
+#include "camel-imap-command.h"
+#include "camel-imap-folder.h"
+#include "camel-imap-summary.h"
+#include "camel-imap-utils.h"
+#include "camel-imap-private.h"
+
+#include "camel-internet-address.h"
+#include "camel-mime-message.h"
+#include "camel-mime-utils.h"
+#include "camel-stream-mem.h"
+
+static const char *imap_protocol_get_summary_specifier (CamelImapStore *store);
+
+static CamelMessageInfo *parse_headers (char **headers_p);
+static CamelMessageContentInfo *parse_body (CamelFolderSummary *summary,
+ char **body_p,
+ const char *part_specifier);
+
+static void skip_astring (char **str_p);
+
+/**
+ * imap_add_to_summary:
+ * @folder: the (IMAP) folder
+ * @first: the sequence number of the first message to add
+ * @last: the sequence number of the last message to add
+ * @changes: a CamelFolderChangeInfo structure to update
+ * @ex: a CamelException
+ *
+ * This fetches information about the messages in the indicated range
+ * and updates the folder's summary information. As a side effect, it may
+ * also cache partial messages in the folder message cache.
+ **/
+void
+imap_add_to_summary (CamelFolder *folder, int first, int last,
+ CamelFolderChangeInfo *changes, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+ CamelImapResponse *response;
+ GPtrArray *headers, *messages;
+ const char *summary_specifier;
+ char *p, *uid;
+ int i, seq;
+ CamelMessageInfo *mi;
+ CamelMessageContentInfo *content;
+ guint32 flags, size;
+
+ summary_specifier = imap_protocol_get_summary_specifier (store);
+ CAMEL_IMAP_STORE_LOCK(store, command_lock);
+ if (first == last) {
+ response = camel_imap_command (store, folder, ex,
+ "FETCH %d (%s)", first,
+ summary_specifier);
+ } else {
+ response = camel_imap_command (store, folder, ex,
+ "FETCH %d:%d (%s)", first,
+ last, summary_specifier);
+ }
+ CAMEL_IMAP_STORE_UNLOCK(store, command_lock);
+ if (!response)
+ return;
+
+ messages = g_ptr_array_new ();
+ g_ptr_array_set_size (messages, last - first + 1);
+ headers = response->untagged;
+ for (i = 0; i < headers->len; i++) {
+ p = headers->pdata[i];
+ if (!g_strncasecmp (p, "* fetch ", 8))
+ continue;
+ seq = strtoul (p + 8, &p, 10);
+ if (!seq || seq < camel_folder_summary_count (folder->summary))
+ continue;
+
+ mi = messages->pdata[seq - first];
+ flags = size = 0;
+ content = NULL;
+ 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, "size ", 5)) {
+ size = strtoul (p + 5, &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 ", 5)) {
+ p += 5;
+ content = parse_body (folder->summary, &p, "");
+ } else if (!g_strncasecmp (p, "body[header] ", 13) ||
+ !g_strncasecmp (p, "rfc822.header ", 14)) {
+ p = strchr (p + 13, ' ');
+ mi = parse_headers (&p);
+ } else {
+ g_warning ("Waiter, I did not order this %.*s",
+ (int)strcspn (p, " \n"), p);
+ p = NULL;
+ }
+ }
+
+ /* 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.
+ */
+ if (mi == NULL) {
+ p = headers->pdata[i];
+ g_ptr_array_remove_index (headers, i);
+ g_ptr_array_add (headers, p);
+ continue;
+ }
+
+ messages->pdata[seq - first] = mi;
+ if (uid)
+ camel_message_info_set_uid (mi, uid);
+ if (flags)
+ mi->flags = flags;
+ if (content)
+ mi->content = content;
+ if (size)
+ mi->size = size;
+ }
+ camel_imap_response_free (response);
+
+ for (i = 0; i < messages->len; i++) {
+ mi = messages->pdata[i];
+ camel_folder_summary_add (folder->summary, mi);
+ }
+ g_ptr_array_free (messages, TRUE);
+}
+
+static const char *
+imap_protocol_get_summary_specifier (CamelImapStore *store)
+{
+ if (store->server_level >= IMAP_LEVEL_IMAP4REV1)
+ return "UID FLAGS RFC822.SIZE BODY BODY.PEEK[HEADER]";
+ else
+ return "UID FLAGS RFC822.SIZE BODY RFC822.HEADER";
+}
+
+/**
+ * skip_char:
+ * @str_p: a pointer to a string
+ * @ch: the character to skip
+ *
+ * Skip the specified character, or fail. Updates the position of
+ * *@str_p on success, sets it to %NULL on failure.
+ **/
+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_astring:
+ * @str_p: a pointer to a string
+ *
+ * Skip an astring, or fail. Updates the position of *@str_p on
+ * success, sets it to %NULL on failure.
+ **/
+static void
+skip_astring (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;
+ }
+}
+
+/**
+ * skip_list:
+ * @str_p: a pointer to the open parenthesis of a list
+ *
+ * Skips over a list of astrings and lists. Updates the position of
+ * *@str_p on success, sets it to %NULL on failure.
+ **/
+void
+skip_list (char **str_p)
+{
+ skip_char (str_p, '(');
+ while (*str_p && **str_p != ')') {
+ if (**str_p == '(')
+ skip_list (str_p);
+ else
+ skip_astring (str_p);
+ if (*str_p && **str_p == ' ')
+ skip_char (str_p, ' ');
+ }
+ skip_char (str_p, ')');
+}
+
+/**
+ * parse_params:
+ * @parms_p: a pointer to the start of an IMAP "body_fld_param".
+ * @ct: a content-type structure
+ *
+ * This parses the body_fld_param and sets parameters on @ct
+ * appropriately.
+ *
+ * On a successful return, *@params_p will be set to point to the
+ * character after the last character of the body_fld_param. On
+ * failure, it will be set to %NULL.
+ **/
+static void
+parse_params (char **parms_p, struct _header_content_type *ct)
+{
+ 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);
+ value = imap_parse_nstring (&parms, &len);
+
+ if (name && value)
+ header_content_type_set_param (ct, name, value);
+ g_free (name);
+ g_free (value);
+
+ if (!parms)
+ break;
+ if (*parms++ != ')') {
+ parms = NULL;
+ break;
+ }
+ }
+
+ if (!parms || *parms++ != ')') {
+ *parms_p = NULL;
+ return;
+ }
+ *parms_p = parms;
+}
+
+static CamelMessageContentInfo *
+parse_body (CamelFolderSummary *summary, char **body_p,
+ const char *part_specifier)
+{
+ char *body = *body_p;
+ CamelMessageContentInfo *ci;
+ CamelImapMessageContentInfo *ici;
+ char *child_specifier;
+ int speclen, len;
+
+ if (*body++ != '(') {
+ *body_p = NULL;
+ return NULL;
+ }
+
+ ci = camel_folder_summary_content_info_new (summary);
+ ici = (CamelImapMessageContentInfo *)ci;
+ ici->part_specifier = g_strdup (part_specifier);
+
+ if (*body == '(') {
+ /* multipart */
+ GPtrArray *children;
+ CamelMessageContentInfo *child;
+ char *subtype;
+ int i;
+
+ speclen = strlen (part_specifier);
+ child_specifier = g_malloc (speclen + 10);
+ memcpy (child_specifier, part_specifier, speclen);
+ child_specifier[speclen] = '.';
+
+ /* Parse the child body parts */
+ children = g_ptr_array_new ();
+ i = 0;
+ while (body && *body == '(') {
+ sprintf (child_specifier + speclen + 1, "%d", i++);
+ child = parse_body (summary, &body, child_specifier);
+ if (!child)
+ break;
+ child->parent = ci;
+ g_ptr_array_add (children, child);
+ }
+ g_free (child_specifier);
+ skip_char (&body, ' ');
+
+ /* If there is a parse error, or there are no children,
+ * abort.
+ */
+ if (!body || !children->len) {
+ for (i = 0; i < children->len; i++) {
+ child = children->pdata[i];
+ camel_folder_summary_content_info_free (summary, child);
+ }
+ g_ptr_array_free (children, TRUE);
+ camel_folder_summary_content_info_free (summary, ci);
+ *body_p = NULL;
+ return NULL;
+ }
+
+ /* Chain the children. */
+ ci->childs = children->pdata[0];
+ for (i = 0; i < children->len - 1; i++) {
+ child = children->pdata[i];
+ child->next = children->pdata[i + 1];
+ }
+ g_ptr_array_free (children, TRUE);
+
+ /* Parse the multipart subtype */
+ subtype = imap_parse_string (&body, &len);
+ ci->type = header_content_type_new ("multipart", subtype);
+ g_free (subtype);
+ } else {
+ /* single part */
+ char *type, *subtype;
+
+ type = imap_parse_string (&body, &len);
+ skip_char (&body, ' ');
+ subtype = imap_parse_string (&body, &len);
+ skip_char (&body, ' ');
+ if (!body) {
+ camel_folder_summary_content_info_free (summary, ci);
+ *body_p = NULL;
+ return NULL;
+ }
+ ci->type = header_content_type_new (type, subtype);
+ parse_params (&body, ci->type);
+ skip_char (&body, ' ');
+
+ ci->id = imap_parse_string (&body, &len);
+ skip_char (&body, ' ');
+ ci->description = imap_parse_string (&body, &len);
+ skip_char (&body, ' ');
+ ci->encoding = imap_parse_string (&body, &len);
+ skip_char (&body, ' ');
+ ci->size = strtoul (body, &body, 10);
+
+ if (header_content_type_is (ci->type, "message", "rfc822")) {
+ skip_char (&body, ' ');
+ ici->message_info = camel_folder_summary_info_new (summary);
+ skip_list (&body); /* envelope */
+ skip_char (&body, ' ');
+ ci->childs = parse_body (summary, &body,
+ part_specifier);
+ skip_char (&body, ' ');
+ strtoul (body, &body, 10);
+ } else if (header_content_type_is (ci->type, "text", "*"))
+ strtoul (body, &body, 10);
+
+ g_free (type);
+ g_free (subtype);
+ }
+
+ if (!body || *body++ != ')') {
+ *body_p = NULL;
+ camel_folder_summary_content_info_free (summary, ci);
+ return NULL;
+ }
+
+ *body_p = body;
+ return ci;
+}
+
+void
+parse_bodypart (char **body_p, CamelFolder *folder, CamelMessageInfo *mi)
+{
+ CamelMessageContentInfo *ci;
+ char *body = *body_p;
+ int num;
+
+ /* Skip "body[" */
+ body += 5;
+ ci = mi->content;
+
+ /* Parse the 'nz_number *["." nz_number]' prefix. This is fun,
+ * because the numbers mean different things depending on where
+ * you are. See RFC 2060 for details.
+ */
+ while (isdigit((unsigned char)*body)) {
+ num = strtoul (body, &body, 10);
+ if (num == 0 || (*body != '.' && *body != ']')) {
+ *body = NULL;
+ return;
+ }
+
+ if (header_content_type_is (ci->type, "multipart", "*")) {
+ ci = ci->childs;
+ while (ci && --num)
+ ci = ci->next;
+ if (!ci) {
+ *body = NULL;
+ return;
+ }
+ } else if (num != 1) {
+ *body = NULL;
+ return;
+ }
+
+ if (*body == ']')
+ break;
+
+ if (isdigit ((unsigned char)*++body) &&
+ header_content_type_is (ci->type, "message", "rfc822")) {
+ mi = ((CamelImapMessageContentInfo *)ci)->message_info;
+ ci = mi->content;
+ }
+ }
+
+ if (!g_strncasecmp (body, "header] ", 8)) {
+ char *headers;
+ int len;
+ CamelMimeMessage *msg;
+ CamelStream *stream;
+
+ body += 9;
+ mi = parse_headers (&body, folder);
+ /* XXX */
+ } else if (!g_strncasecmp (body, "] ", 2)) {
+ CamelImapMessageContentInfo *ici = (CamelImapMessageContentInfo *)ci;
+ body += 2;
+ /* XXX */
+ } else
+ *body = NULL;
+}
+
+CamelMessageInfo *
+imap_parse_headers (char **headers_p, CamelFolder *folder)
+{
+ CamelMimeMessage *msg;
+ CamelStream *stream;
+ char *headers;
+ int len;
+
+ headers = imap_parse_nstring (headers_p, &len);
+ msg = camel_mime_message_new ();
+ stream = camel_stream_mem_new_with_buffer (headers, len);
+ g_free (headers);
+ camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg), stream);
+ camel_object_unref (CAMEL_OBJECT (stream));
+
+ mi = camel_folder_summary_info_new_from_message (folder->summary, msg);
+ camel_imap_folder_cache_message (folder, mi, msg);
+}