/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* Camel
* Copyright (C) 1999-2004 Jeffrey Stedfast
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>
#include <utime.h>
#include <fcntl.h>
#include <ctype.h>
#include <e-util/md5-utils.h>
#include <camel/camel-file-utils.h>
#include <camel/camel-i18n.h>
#include "camel-imap4-store.h"
#include "camel-imap4-engine.h"
#include "camel-imap4-folder.h"
#include "camel-imap4-stream.h"
#include "camel-imap4-command.h"
#include "camel-imap4-utils.h"
#include "camel-imap4-summary.h"
#define IMAP4_SUMMARY_VERSION 1
static void camel_imap4_summary_class_init (CamelIMAP4SummaryClass *klass);
static void camel_imap4_summary_init (CamelIMAP4Summary *summary, CamelIMAP4SummaryClass *klass);
static void camel_imap4_summary_finalize (CamelObject *object);
static int imap4_header_load (CamelFolderSummary *summary, FILE *fin);
static int imap4_header_save (CamelFolderSummary *summary, FILE *fout);
static CamelMessageInfo *imap4_message_info_new (CamelFolderSummary *summary, struct _camel_header_raw *header);
static CamelMessageInfo *imap4_message_info_load (CamelFolderSummary *summary, FILE *fin);
static int imap4_message_info_save (CamelFolderSummary *summary, FILE *fout, CamelMessageInfo *info);
static CamelFolderSummaryClass *parent_class = NULL;
CamelType
camel_imap4_summary_get_type (void)
{
static CamelType type = 0;
if (!type) {
type = camel_type_register (CAMEL_FOLDER_SUMMARY_TYPE,
"CamelIMAP4Summary",
sizeof (CamelIMAP4Summary),
sizeof (CamelIMAP4SummaryClass),
(CamelObjectClassInitFunc) camel_imap4_summary_class_init,
NULL,
(CamelObjectInitFunc) camel_imap4_summary_init,
(CamelObjectFinalizeFunc) camel_imap4_summary_finalize);
}
return type;
}
static void
camel_imap4_summary_class_init (CamelIMAP4SummaryClass *klass)
{
CamelFolderSummaryClass *summary_class = (CamelFolderSummaryClass *) klass;
parent_class = (CamelFolderSummaryClass *) camel_type_get_global_classfuncs (camel_folder_summary_get_type ());
summary_class->summary_header_load = imap4_header_load;
summary_class->summary_header_save = imap4_header_save;
summary_class->message_info_new = imap4_message_info_new;
summary_class->message_info_load = imap4_message_info_load;
summary_class->message_info_save = imap4_message_info_save;
}
static void
camel_imap4_summary_init (CamelIMAP4Summary *summary, CamelIMAP4SummaryClass *klass)
{
CamelFolderSummary *folder_summary = (CamelFolderSummary *) summary;
folder_summary->version += IMAP4_SUMMARY_VERSION;
folder_summary->flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_DELETED |
CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN;
folder_summary->message_info_size = sizeof (CamelIMAP4MessageInfo);
summary->update_flags = TRUE;
summary->uidvalidity_changed = FALSE;
}
static void
camel_imap4_summary_finalize (CamelObject *object)
{
;
}
CamelFolderSummary *
camel_imap4_summary_new (CamelFolder *folder)
{
CamelFolderSummary *summary;
summary = (CamelFolderSummary *) camel_object_new (CAMEL_TYPE_IMAP4_SUMMARY);
((CamelIMAP4Summary *) summary)->folder = folder;
return summary;
}
static int
imap4_header_load (CamelFolderSummary *summary, FILE *fin)
{
CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
if (CAMEL_FOLDER_SUMMARY_CLASS (parent_class)->summary_header_load (summary, fin) == -1)
return -1;
if (camel_file_util_decode_uint32 (fin, &imap4_summary->uidvalidity) == -1)
return -1;
return 0;
}
static int
imap4_header_save (CamelFolderSummary *summary, FILE *fout)
{
CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
if (CAMEL_FOLDER_SUMMARY_CLASS (parent_class)->summary_header_save (summary, fout) == -1)
return -1;
if (camel_file_util_encode_uint32 (fout, imap4_summary->uidvalidity) == -1)
return -1;
return 0;
}
static int
envelope_decode_address (CamelIMAP4Engine *engine, GString *addrs, CamelException *ex)
{
char *addr, *name = NULL, *user = NULL;
struct _camel_header_address *cia;
camel_imap4_token_t token;
const char *domain = NULL;
int part = 0;
if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
return -1;
if (token.token == CAMEL_IMAP4_TOKEN_NIL) {
return 0;
} else if (token.token != '(') {
camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
return -1;
}
if (addrs->len > 0)
g_string_append (addrs, ", ");
do {
if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
return -1;
switch (token.token) {
case CAMEL_IMAP4_TOKEN_NIL:
break;
case CAMEL_IMAP4_TOKEN_ATOM:
case CAMEL_IMAP4_TOKEN_QSTRING:
switch (part) {
case 0:
name = camel_header_decode_string (token.v.qstring, NULL);
break;
case 2:
user = g_strdup (token.v.qstring);
break;
case 3:
domain = token.v.qstring;
break;
}
break;
default:
camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
g_free (name);
g_free (user);
return -1;
}
part++;
} while (part < 4);
addr = g_strdup_printf ("%s@%s", user, domain);
g_free (user);
cia = camel_header_address_new_name (name, addr);
g_free (name);
g_free (addr);
addr = camel_header_address_list_format (cia);
camel_header_address_unref (cia);
g_string_append (addrs, addr);
g_free (addr);
if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
return -1;
if (token.token != ')') {
camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
return -1;
}
return 0;
}
static int
envelope_decode_addresses (CamelIMAP4Engine *engine, char **addrlist, CamelException *ex)
{
camel_imap4_token_t token;
GString *addrs;
if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
return -1;
if (token.token == CAMEL_IMAP4_TOKEN_NIL) {
*addrlist = NULL;
return 0;
} else if (token.token != '(') {
camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
return -1;
}
addrs = g_string_new ("");
do {
if (camel_imap4_engine_next_token (engine, &token, ex) == -1) {
g_string_free (addrs, TRUE);
return -1;
}
if (token.token == '(') {
camel_imap4_stream_unget_token (engine->istream, &token);
if (envelope_decode_address (engine, addrs, ex) == -1) {
g_string_free (addrs, TRUE);
return -1;
}
} else if (token.token == ')') {
break;
} else {
camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
return -1;
}
} while (1);
*addrlist = addrs->str;
g_string_free (addrs, FALSE);
return 0;
}
static int
envelope_decode_date (CamelIMAP4Engine *engine, time_t *date, CamelException *ex)
{
camel_imap4_token_t token;
const char *nstring;
if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
return -1;
switch (token.token) {
case CAMEL_IMAP4_TOKEN_NIL:
*date = (time_t) -1;
return 0;
case CAMEL_IMAP4_TOKEN_ATOM:
nstring = token.v.atom;
break;
case CAMEL_IMAP4_TOKEN_QSTRING:
nstring = token.v.qstring;
break;
default:
camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
return -1;
}
*date = camel_header_decode_date (nstring, NULL);
return 0;
}
static int
envelope_decode_nstring (CamelIMAP4Engine *engine, char **nstring, gboolean rfc2047, CamelException *ex)
{
camel_imap4_token_t token;
if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
return -1;
switch (token.token) {
case CAMEL_IMAP4_TOKEN_NIL:
*nstring = NULL;
break;
case CAMEL_IMAP4_TOKEN_ATOM:
if (rfc2047)
*nstring = camel_header_decode_string (token.v.atom, NULL);
else
*nstring = g_strdup (token.v.atom);
break;
case CAMEL_IMAP4_TOKEN_QSTRING:
if (rfc2047)
*nstring = camel_header_decode_string (token.v.qstring, NULL);
else
*nstring = g_strdup (token.v.qstring);
break;
default:
camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
return -1;
}
return 0;
}
static CamelSummaryReferences *
decode_references (const char *string)
{
struct _camel_header_references *refs, *r;
CamelSummaryReferences *references;
unsigned char md5sum[16];
guint32 i, n = 0;
MD5Context md5;
if (!(r = refs = camel_header_references_inreplyto_decode (string)))
return NULL;
while (r != NULL) {
r = r->next;
n++;
}
references = g_malloc (sizeof (CamelSummaryReferences) + (sizeof (CamelSummaryMessageID) * (n - 1)));
references->size = n;
for (i = 0, r = refs; i < n; i++, r = r->next) {
md5_init (&md5);
md5_update (&md5, r->id, strlen (r->id));
md5_final (&md5, md5sum);
memcpy (references->references[i].id.hash, md5sum, sizeof (references->references[i].id.hash));
}
camel_header_references_list_clear (&refs);
return references;
}
static int
decode_envelope (CamelIMAP4Engine *engine, CamelMessageInfo *info, camel_imap4_token_t *token, CamelException *ex)
{
unsigned char md5sum[16];
char *nstring;
if (camel_imap4_engine_next_token (engine, token, ex) == -1)
return -1;
if (token->token != '(') {
camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
return -1;
}
if (envelope_decode_date (engine, &info->date_sent, ex) == -1)
goto exception;
/* subject */
if (envelope_decode_nstring (engine, &nstring, TRUE, ex) == -1)
goto exception;
camel_message_info_set_subject (info, nstring);
/* from */
if (envelope_decode_addresses (engine, &nstring, ex) == -1)
goto exception;
camel_message_info_set_from (info, nstring);
/* sender */
if (envelope_decode_addresses (engine, &nstring, ex) == -1)
goto exception;
g_free (nstring);
/* reply-to */
if (envelope_decode_addresses (engine, &nstring, ex) == -1)
goto exception;
g_free (nstring);
/* to */
if (envelope_decode_addresses (engine, &nstring, ex) == -1)
goto exception;
camel_message_info_set_to (info, nstring);
/* cc */
if (envelope_decode_addresses (engine, &nstring, ex) == -1)
goto exception;
camel_message_info_set_cc (info, nstring);
/* bcc */
if (envelope_decode_addresses (engine, &nstring, ex) == -1)
goto exception;
g_free (nstring);
/* in-reply-to */
if (envelope_decode_nstring (engine, &nstring, FALSE, ex) == -1)
goto exception;
if (nstring != NULL) {
info->references = decode_references (nstring);
g_free (nstring);
}
/* message-id */
if (envelope_decode_nstring (engine, &nstring, FALSE, ex) == -1)
goto exception;
if (nstring != NULL) {
md5_get_digest (nstring, strlen (nstring), md5sum);
memcpy (info->message_id.id.hash, md5sum, sizeof (info->message_id.id.hash));
g_free (nstring);
}
if (camel_imap4_engine_next_token (engine, token, ex) == -1)
return -1;
if (token->token != ')') {
camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
goto exception;
}
return 0;
exception:
return -1;
}
static char *tm_months[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static gboolean
decode_time (const char **in, int *hour, int *min, int *sec)
{
register const unsigned char *inptr = (const unsigned char *) *in;
int *val, colons = 0;
*hour = *min = *sec = 0;
val = hour;
for ( ; *inptr && !isspace ((int) *inptr); inptr++) {
if (*inptr == ':') {
colons++;
switch (colons) {
case 1:
val = min;
break;
case 2:
val = sec;
break;
default:
return FALSE;
}
} else if (!isdigit ((int) *inptr))
return FALSE;
else
*val = (*val * 10) + (*inptr - '0');
}
*in = inptr;
return TRUE;
}
static time_t
mktime_utc (struct tm *tm)
{
time_t tt;
tm->tm_isdst = -1;
tt = mktime (tm);
#if defined (HAVE_TM_GMTOFF)
tt += tm->tm_gmtoff;
#elif defined (HAVE_TIMEZONE)
if (tm->tm_isdst > 0) {
#if defined (HAVE_ALTZONE)
tt -= altzone;
#else /* !defined (HAVE_ALTZONE) */
tt -= (timezone - 3600);
#endif
} else
tt -= timezone;
#endif
return tt;
}
static time_t
decode_internaldate (const char *in)
{
const char *inptr = in;
int hour, min, sec, n;
struct tm tm;
time_t date;
char *buf;
memset ((void *) &tm, 0, sizeof (struct tm));
tm.tm_mday = strtoul (inptr, &buf, 10);
if (buf == inptr || *buf != '-')
return (time_t) -1;
inptr = buf + 1;
if (inptr[3] != '-')
return (time_t) -1;
for (n = 0; n < 12; n++) {
if (!strncasecmp (inptr, tm_months[n], 3))
break;
}
if (n >= 12)
return (time_t) -1;
tm.tm_mon = n;
inptr += 4;
n = strtoul (inptr, &buf, 10);
if (buf == inptr || *buf != ' ')
return (time_t) -1;
tm.tm_year = n - 1900;
inptr = buf + 1;
if (!decode_time (&inptr, &hour, &min, &sec))
return (time_t) -1;
tm.tm_hour = hour;
tm.tm_min = min;
tm.tm_sec = sec;
n = strtol (inptr, NULL, 10);
date = mktime_utc (&tm);
/* date is now GMT of the time we want, but not offset by the timezone ... */
/* this should convert the time to the GMT equiv time */
date -= ((n / 100) * 60 * 60) + (n % 100) * 60;
return date;
}
enum {
IMAP4_FETCH_ENVELOPE = (1 << 1),
IMAP4_FETCH_FLAGS = (1 << 2),
IMAP4_FETCH_INTERNALDATE = (1 << 3),
IMAP4_FETCH_RFC822SIZE = (1 << 4),
IMAP4_FETCH_UID = (1 << 5),
};
#define IMAP4_FETCH_ALL (IMAP4_FETCH_ENVELOPE | IMAP4_FETCH_FLAGS | IMAP4_FETCH_INTERNALDATE | IMAP4_FETCH_RFC822SIZE | IMAP4_FETCH_UID)
struct imap4_envelope_t {
CamelMessageInfo *info;
guint changed;
};
struct imap4_fetch_all_t {
CamelFolderChangeInfo *changes;
CamelFolderSummary *summary;
GHashTable *uid_hash;
GPtrArray *added;
guint32 first;
guint32 need;
int count;
int total;
};
static void
imap4_fetch_all_free (struct imap4_fetch_all_t *fetch)
{
struct imap4_envelope_t *envelope;
int i;
for (i = 0; i < fetch->added->len; i++) {
if (!(envelope = fetch->added->pdata[i]))
continue;
camel_folder_summary_info_free (fetch->summary, envelope->info);
g_free (envelope);
}
g_ptr_array_free (fetch->added, TRUE);
g_hash_table_destroy (fetch->uid_hash);
camel_folder_change_info_free (fetch->changes);
g_free (fetch);
}
static void
courier_imap_is_a_piece_of_shit (CamelFolderSummary *summary, guint32 msg)
{
CamelIMAP4Summary *imap = (CamelIMAP4Summary *) summary;
CamelSession *session = ((CamelService *) ((CamelFolder *) imap->folder)->parent_store)->session;
char *warning;
warning = g_strdup_printf ("IMAP server did not respond with an untagged FETCH response "
"for message #%u. This is illegal according to rfc3501 (and "
"the older rfc2060). You will need to contact your\n"
"Administrator(s) (or ISP) and have them resolve this issue.\n\n"
"Hint: If your IMAP server is Courier-IMAP, it is likely that this "
"message is simply unreadable by the IMAP server and will need "
"to be given read permissions.", msg);
camel_session_alert_user (session, CAMEL_SESSION_ALERT_WARNING, warning, FALSE);
g_free (warning);
}
static void
imap4_fetch_all_add (struct imap4_fetch_all_t *fetch)
{
CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) fetch->summary;
CamelFolderChangeInfo *changes = NULL;
struct imap4_envelope_t *envelope;
CamelMessageInfo *info;
int i;
changes = fetch->changes;
for (i = 0; i < fetch->added->len; i++) {
if (!(envelope = fetch->added->pdata[i])) {
courier_imap_is_a_piece_of_shit (fetch->summary, i + fetch->first);
break;
}
if (envelope->changed != IMAP4_FETCH_ALL) {
fprintf (stderr, "Hmmm, IMAP4 server didn't give us everything for message %d\n", i + 1);
camel_folder_summary_info_free (fetch->summary, envelope->info);
g_free (envelope);
continue;
}
if ((info = camel_folder_summary_uid (fetch->summary, camel_message_info_uid (envelope->info)))) {
camel_folder_summary_info_free (fetch->summary, envelope->info);
g_free (envelope);
continue;
}
camel_folder_change_info_add_uid (changes, camel_message_info_uid (envelope->info));
camel_folder_summary_add (fetch->summary, envelope->info);
g_free (envelope);
}
g_ptr_array_free (fetch->added, TRUE);
g_hash_table_destroy (fetch->uid_hash);
if (camel_folder_change_info_changed (changes))
camel_object_trigger_event (imap4_summary->folder, "folder_changed", changes);
camel_folder_change_info_free (changes);
g_free (fetch);
}
static guint32
imap4_fetch_all_update (struct imap4_fetch_all_t *fetch)
{
CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) fetch->summary;
CamelIMAP4MessageInfo *iinfo, *new_iinfo;
CamelFolderChangeInfo *changes = NULL;
struct imap4_envelope_t *envelope;
CamelMessageInfo *info;
guint32 first = 0;
guint32 flags;
int scount, i;
changes = fetch->changes;
scount = camel_folder_summary_count (fetch->summary);
for (i = fetch->first - 1; i < scount; i++) {
info = camel_folder_summary_index (fetch->summary, i);
if (!(envelope = g_hash_table_lookup (fetch->uid_hash, camel_message_info_uid (info)))) {
/* remove it */
camel_folder_change_info_remove_uid (changes, camel_message_info_uid (info));
camel_folder_summary_remove (fetch->summary, info);
scount--;
i--;
} else if (envelope->changed & IMAP4_FETCH_FLAGS) {
/* update it with the new flags */
new_iinfo = (CamelIMAP4MessageInfo *) envelope->info;
iinfo = (CamelIMAP4MessageInfo *) info;
flags = info->flags;
info->flags = camel_imap4_merge_flags (iinfo->server_flags, info->flags, new_iinfo->server_flags);
iinfo->server_flags = new_iinfo->server_flags;
if (info->flags != flags)
camel_folder_change_info_change_uid (changes, camel_message_info_uid (info));
}
camel_folder_summary_info_free (fetch->summary, info);
}
for (i = 0; i < fetch->added->len; i++) {
if (!(envelope = fetch->added->pdata[i])) {
courier_imap_is_a_piece_of_shit (fetch->summary, i + fetch->first);
break;
}
info = envelope->info;
if (!first && camel_message_info_uid (info)) {
if ((info = camel_folder_summary_uid (fetch->summary, camel_message_info_uid (info)))) {
camel_folder_summary_info_free (fetch->summary, info);
} else {
first = i + fetch->first;
}
}
camel_folder_summary_info_free (fetch->summary, envelope->info);
g_free (envelope);
}
g_ptr_array_free (fetch->added, TRUE);
g_hash_table_destroy (fetch->uid_hash);
if (camel_folder_change_info_changed (changes))
camel_object_trigger_event (imap4_summary->folder, "folder_changed", changes);
camel_folder_change_info_free (changes);
g_free (fetch);
return first;
}
static int
untagged_fetch_all (CamelIMAP4Engine *engine, CamelIMAP4Command *ic, guint32 index, camel_imap4_token_t *token, CamelException *ex)
{
struct imap4_fetch_all_t *fetch = ic->user_data;
CamelFolderSummary *summary = fetch->summary;
struct imap4_envelope_t *envelope = NULL;
GPtrArray *added = fetch->added;
CamelIMAP4MessageInfo *iinfo;
CamelMessageInfo *info;
guint32 changed = 0;
const char *iuid;
char uid[12];
if (index < fetch->first) {
/* we already have this message envelope cached -
* server is probably notifying us of a FLAGS change
* by another client? */
g_assert (index < summary->messages->len);
iinfo = (CamelIMAP4MessageInfo *)(info = summary->messages->pdata[index - 1]);
g_assert (info != NULL);
} else {
if (index > (added->len + fetch->first - 1))
g_ptr_array_set_size (added, index - fetch->first + 1);
if (!(envelope = added->pdata[index - fetch->first])) {
iinfo = (CamelIMAP4MessageInfo *) (info = camel_folder_summary_info_new (summary));
envelope = g_new (struct imap4_envelope_t, 1);
added->pdata[index - fetch->first] = envelope;
envelope->info = info;
envelope->changed = 0;
} else {
iinfo = (CamelIMAP4MessageInfo *) (info = envelope->info);
}
}
if (camel_imap4_engine_next_token (engine, token, ex) == -1)
return -1;
/* parse the FETCH response list */
if (token->token != '(') {
camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
return -1;
}
do {
if (camel_imap4_engine_next_token (engine, token, ex) == -1)
goto exception;
if (token->token == ')' || token->token == '\n')
break;
if (token->token != CAMEL_IMAP4_TOKEN_ATOM)
goto unexpected;
if (!strcmp (token->v.atom, "ENVELOPE")) {
if (envelope) {
if (decode_envelope (engine, info, token, ex) == -1)
goto exception;
changed |= IMAP4_FETCH_ENVELOPE;
} else {
CamelMessageInfo *tmp;
int rv;
g_warning ("Hmmm, server is sending us ENVELOPE data for a message we didn't ask for (message %u)\n",
index);
tmp = camel_folder_summary_info_new (summary);
rv = decode_envelope (engine, tmp, token, ex);
camel_folder_summary_info_free (summary, tmp);
if (rv == -1)
goto exception;
}
} else if (!strcmp (token->v.atom, "FLAGS")) {
guint32 server_flags = 0;
if (camel_imap4_parse_flags_list (engine, &server_flags, ex) == -1)
return -1;
info->flags = camel_imap4_merge_flags (iinfo->server_flags, info->flags, server_flags);
iinfo->server_flags = server_flags;
changed |= IMAP4_FETCH_FLAGS;
} else if (!strcmp (token->v.atom, "INTERNALDATE")) {
if (camel_imap4_engine_next_token (engine, token, ex) == -1)
goto exception;
switch (token->token) {
case CAMEL_IMAP4_TOKEN_NIL:
info->date_received = (time_t) -1;
break;
case CAMEL_IMAP4_TOKEN_ATOM:
case CAMEL_IMAP4_TOKEN_QSTRING:
info->date_received = decode_internaldate (token->v.qstring);
break;
default:
goto unexpected;
}
changed |= IMAP4_FETCH_INTERNALDATE;
} else if (!strcmp (token->v.atom, "RFC822.SIZE")) {
if (camel_imap4_engine_next_token (engine, token, ex) == -1)
goto exception;
if (token->token != CAMEL_IMAP4_TOKEN_NUMBER)
goto unexpected;
info->size = token->v.number;
changed |= IMAP4_FETCH_RFC822SIZE;
} else if (!strcmp (token->v.atom, "UID")) {
if (camel_imap4_engine_next_token (engine, token, ex) == -1)
goto exception;
if (token->token != CAMEL_IMAP4_TOKEN_NUMBER || token->v.number == 0)
goto unexpected;
sprintf (uid, "%u", token->v.number);
iuid = camel_message_info_uid (info);
if (iuid != NULL && iuid[0] != '\0') {
if (strcmp (iuid, uid) != 0) {
fprintf (stderr, "Hmmm, UID mismatch for message %u\n", index);
g_assert_not_reached ();
}
} else {
camel_message_info_set_uid (info, g_strdup (uid));
g_hash_table_insert (fetch->uid_hash, (void *) camel_message_info_uid (info), envelope);
changed |= IMAP4_FETCH_UID;
}
} else {
/* wtf? */
fprintf (stderr, "huh? %s?...\n", token->v.atom);
}
} while (1);
if (envelope) {
envelope->changed |= changed;
if ((envelope->changed & fetch->need) == fetch->need)
camel_operation_progress (NULL, (++fetch->count * 100.0f) / fetch->total);
} else if (changed & IMAP4_FETCH_FLAGS) {
camel_folder_change_info_change_uid (fetch->changes, camel_message_info_uid (info));
}
if (token->token != ')')
goto unexpected;
return 0;
unexpected:
camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
exception:
return -1;
}
static CamelIMAP4Command *
imap4_summary_fetch_all (CamelFolderSummary *summary, guint32 first, guint32 last)
{
CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
CamelFolder *folder = imap4_summary->folder;
struct imap4_fetch_all_t *fetch;
CamelIMAP4Engine *engine;
CamelIMAP4Command *ic;
int total;
engine = ((CamelIMAP4Store *) folder->parent_store)->engine;
/* FIXME: would be a nice optimisation if we could size the
* 'added' array here rather than possibly having to grow it
* one element at a time (in the common case) in the
* untagged_fetch_all() callback */
total = last ? (last - first) + 1 : (imap4_summary->exists - first) + 1;
fetch = g_new (struct imap4_fetch_all_t, 1);
fetch->uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
fetch->changes = camel_folder_change_info_new ();
fetch->added = g_ptr_array_sized_new (total);
fetch->summary = summary;
fetch->first = first;
fetch->need = IMAP4_FETCH_ALL;
fetch->total = total;
fetch->count = 0;
/* From rfc2060, Section 6.4.5:
*
* The currently defined data items that can be fetched are:
*
* ALL Macro equivalent to: (FLAGS INTERNALDATE
* RFC822.SIZE ENVELOPE)
**/
if (last != 0)
ic = camel_imap4_engine_queue (engine, folder, "FETCH %u:%u (UID ALL)\r\n", first, last);
else
ic = camel_imap4_engine_queue (engine, folder, "FETCH %u:* (UID ALL)\r\n", first);
camel_imap4_command_register_untagged (ic, "FETCH", untagged_fetch_all);
ic->user_data = fetch;
return ic;
}
static CamelIMAP4Command *
imap4_summary_fetch_flags (CamelFolderSummary *summary, guint32 first, guint32 last)
{
CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
CamelFolder *folder = imap4_summary->folder;
struct imap4_fetch_all_t *fetch;
CamelIMAP4Engine *engine;
CamelIMAP4Command *ic;
int total;
engine = ((CamelIMAP4Store *) folder->parent_store)->engine;
/* FIXME: would be a nice optimisation if we could size the
* 'added' array here rather than possibly having to grow it
* one element at a time (in the common case) in the
* untagged_fetch_all() callback */
total = (last - first) + 1;
fetch = g_new (struct imap4_fetch_all_t, 1);
fetch->uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
fetch->changes = camel_folder_change_info_new ();
fetch->added = g_ptr_array_sized_new (total);
fetch->summary = summary;
fetch->first = first;
fetch->need = IMAP4_FETCH_UID | IMAP4_FETCH_FLAGS;
fetch->total = total;
fetch->count = 0;
if (last != 0)
ic = camel_imap4_engine_queue (engine, folder, "FETCH %u:%u (UID FLAGS)\r\n", first, last);
else
ic = camel_imap4_engine_queue (engine, folder, "FETCH %u:* (UID FLAGS)\r\n", first);
camel_imap4_command_register_untagged (ic, "FETCH", untagged_fetch_all);
ic->user_data = fetch;
return ic;
}
#if 0
static int
imap4_build_summary (CamelFolderSummary *summary, guint32 first, guint32 last)
{
CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
CamelFolder *folder = imap4_summary->folder;
struct imap4_fetch_all_t *fetch;
CamelIMAP4Engine *engine;
CamelIMAP4Command *ic;
int id;
engine = ((CamelIMAP4Store *) folder->store)->engine;
ic = imap4_summary_fetch_all (summary, first, last);
while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
;
fetch = ic->user_data;
if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
camel_imap4_command_unref (ic);
imap4_fetch_all_free (fetch);
return -1;
}
imap4_fetch_all_add (fetch);
camel_imap4_command_unref (ic);
return 0;
}
#endif
static CamelMessageInfo *
imap4_message_info_new (CamelFolderSummary *summary, struct _camel_header_raw *header)
{
CamelMessageInfo *info;
info = CAMEL_FOLDER_SUMMARY_CLASS (parent_class)->message_info_new (summary, header);
((CamelIMAP4MessageInfo *) info)->server_flags = 0;
return info;
}
static CamelMessageInfo *
imap4_message_info_load (CamelFolderSummary *summary, FILE *fin)
{
CamelIMAP4MessageInfo *minfo;
CamelMessageInfo *info;
if (!(info = CAMEL_FOLDER_SUMMARY_CLASS (parent_class)->message_info_load (summary, fin)))
return NULL;
minfo = (CamelIMAP4MessageInfo *) info;
if (camel_file_util_decode_uint32 (fin, &minfo->server_flags) == -1)
goto exception;
return info;
exception:
camel_folder_summary_info_free (summary, info);
return NULL;
}
static int
imap4_message_info_save (CamelFolderSummary *summary, FILE *fout, CamelMessageInfo *info)
{
CamelIMAP4MessageInfo *minfo = (CamelIMAP4MessageInfo *) info;
if (CAMEL_FOLDER_SUMMARY_CLASS (parent_class)->message_info_save (summary, fout, info) == -1)
return -1;
if (camel_file_util_encode_uint32 (fout, minfo->server_flags) == -1)
return -1;
return 0;
}
void
camel_imap4_summary_set_exists (CamelFolderSummary *summary, guint32 exists)
{
CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
g_return_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary));
imap4_summary->exists = exists;
}
void
camel_imap4_summary_set_recent (CamelFolderSummary *summary, guint32 recent)
{
CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
g_return_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary));
imap4_summary->recent = recent;
}
void
camel_imap4_summary_set_unseen (CamelFolderSummary *summary, guint32 unseen)
{
CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
g_return_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary));
imap4_summary->unseen = unseen;
}
void
camel_imap4_summary_set_uidnext (CamelFolderSummary *summary, guint32 uidnext)
{
g_return_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary));
summary->nextuid = uidnext;
}
void
camel_imap4_summary_set_uidvalidity (CamelFolderSummary *summary, guint32 uidvalidity)
{
CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
CamelFolderChangeInfo *changes;
CamelMessageInfo *info;
int i, count;
g_return_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary));
if (imap4_summary->uidvalidity == uidvalidity)
return;
changes = camel_folder_change_info_new ();
count = camel_folder_summary_count (summary);
for (i = 0; i < count; i++) {
if (!(info = camel_folder_summary_index (summary, i)))
continue;
camel_folder_change_info_remove_uid (changes, camel_message_info_uid (info));
camel_folder_summary_info_free (summary, info);
}
camel_folder_summary_clear (summary);
if (camel_folder_change_info_changed (changes))
camel_object_trigger_event (imap4_summary->folder, "folder_changed", changes);
camel_folder_change_info_free (changes);
imap4_summary->uidvalidity = uidvalidity;
imap4_summary->uidvalidity_changed = TRUE;
}
void
camel_imap4_summary_expunge (CamelFolderSummary *summary, int seqid)
{
CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
CamelFolderChangeInfo *changes;
CamelMessageInfo *info;
g_return_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary));
seqid--;
if (!(info = camel_folder_summary_index (summary, seqid)))
return;
imap4_summary->exists--;
changes = camel_folder_change_info_new ();
camel_folder_change_info_remove_uid (changes, camel_message_info_uid (info));
camel_folder_summary_info_free (summary, info);
camel_folder_summary_remove_index (summary, seqid);
camel_object_trigger_event (imap4_summary->folder, "folder_changed", changes);
camel_folder_change_info_free (changes);
}
#if 0
static int
info_uid_sort (const CamelMessageInfo **info0, const CamelMessageInfo **info1)
{
guint32 uid0, uid1;
uid0 = strtoul (camel_message_info_uid (*info0), NULL, 10);
uid1 = strtoul (camel_message_info_uid (*info1), NULL, 10);
if (uid0 == uid1)
return 0;
return uid0 < uid1 ? -1 : 1;
}
#endif
int
camel_imap4_summary_flush_updates (CamelFolderSummary *summary, CamelException *ex)
{
CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
CamelIMAP4Engine *engine;
CamelIMAP4Command *ic;
guint32 first = 0;
int scount, id;
g_return_val_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary), -1);
engine = ((CamelIMAP4Store *) imap4_summary->folder->parent_store)->engine;
scount = camel_folder_summary_count (summary);
if (imap4_summary->uidvalidity_changed) {
first = 1;
} else if (imap4_summary->update_flags || imap4_summary->exists < scount) {
/* this both updates flags and removes messages which
* have since been expunged from the server by another
* client */
ic = imap4_summary_fetch_flags (summary, 1, scount);
camel_operation_start (NULL, _("Scanning for changed messages"));
while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
;
if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
imap4_fetch_all_free (ic->user_data);
camel_exception_xfer (ex, &ic->ex);
camel_imap4_command_unref (ic);
camel_operation_end (NULL);
return -1;
}
if (!(first = imap4_fetch_all_update (ic->user_data)) && imap4_summary->exists > scount)
first = scount + 1;
camel_imap4_command_unref (ic);
camel_operation_end (NULL);
} else {
first = scount + 1;
}
if (first != 0 && first <= imap4_summary->exists) {
ic = imap4_summary_fetch_all (summary, first, 0);
camel_operation_start (NULL, _("Fetching envelopes for new messages"));
while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
;
if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
imap4_fetch_all_free (ic->user_data);
camel_exception_xfer (ex, &ic->ex);
camel_imap4_command_unref (ic);
camel_operation_end (NULL);
return -1;
}
imap4_fetch_all_add (ic->user_data);
camel_imap4_command_unref (ic);
camel_operation_end (NULL);
#if 0
/* Note: this should not be needed - the code that adds envelopes to the summary
* adds them in proper order */
/* it's important for these to be sorted sequentially for EXPUNGE events to work */
g_ptr_array_sort (summary->messages, (GCompareFunc) info_uid_sort);
#endif
}
imap4_summary->update_flags = FALSE;
imap4_summary->uidvalidity_changed = FALSE;
return 0;
}