summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNot Zed <NotZed@Ximian.com>2004-09-23 04:12:30 +0000
committerMichael Zucci <zucchi@src.gnome.org>2004-09-23 04:12:30 +0000
commitc607f54b43b3c30f000030155a56a24f1e895259 (patch)
tree76a149a0865beff8add29f8e342ec548ac7917ea
parent078dc566198a67680be5cfaf4b205a7e4fc89bb3 (diff)
downloadevolution-data-server-EVOLUTION_2_0_1.tar.gz
** See bug #47821.EVOLUTION_2_0_1
2004-09-13 Not Zed <NotZed@Ximian.com> ** See bug #47821. * camel-service.c: removed the old hostent based hostname interfaces. * camel-sasl-kerberos4.c (krb4_challenge): new hostname interfaces. * camel-sasl-gssapi.c (gssapi_challenge): new hostname interfaces. * camel-sasl-digest-md5.c (digest_md5_challenge): use new hostname interfaces. (generate_response): just take hostname directly, not hostent. * camel-mime-utils.c (camel_header_msgid_generate): use new hostname interfaces. * providers/smtp/camel-smtp-transport.c (connect_to_server): fixed to use new addrinfo apis. * providers/pop3/camel-pop3-store.c (connect_to_server): fixed to use new addrinfo apis. * camel-tcp-stream-ssl.c (stream_connect): try all addresses supplied. * camel-tcp-stream.c (camel_tcp_stream_get_remote_address) (camel_tcp_stream_get_local_address): return a sockaddr now, and also the address length. Fixed all implementations and callers. (camel_tcp_stream_connect): use addrinfo rather than hostent for host AND port info. Fixed all implementations and callers.
-rw-r--r--camel/ChangeLog32
-rw-r--r--camel/camel-http-stream.c29
-rw-r--r--camel/camel-mime-utils.c4314
-rw-r--r--camel/camel-sasl-digest-md5.c22
-rw-r--r--camel/camel-sasl-gssapi.c346
-rw-r--r--camel/camel-sasl-kerberos4.c14
-rw-r--r--camel/camel-service.c582
-rw-r--r--camel/camel-service.h61
-rw-r--r--camel/camel-tcp-stream-raw.c147
-rw-r--r--camel/camel-tcp-stream-ssl.c160
-rw-r--r--camel/camel-tcp-stream.c89
-rw-r--r--camel/camel-tcp-stream.h29
-rw-r--r--camel/providers/imap/camel-imap-store.c3258
-rw-r--r--camel/providers/imapp/camel-imapp-store.c36
-rw-r--r--camel/providers/nntp/camel-nntp-store.c34
-rw-r--r--camel/providers/pop3/camel-pop3-store.c51
-rw-r--r--camel/providers/smtp/camel-smtp-transport.c94
-rw-r--r--camel/providers/smtp/camel-smtp-transport.h13
18 files changed, 8682 insertions, 629 deletions
diff --git a/camel/ChangeLog b/camel/ChangeLog
index df85f4a34..668fea1d9 100644
--- a/camel/ChangeLog
+++ b/camel/ChangeLog
@@ -1,3 +1,35 @@
+2004-09-13 Not Zed <NotZed@Ximian.com>
+
+ ** See bug #47821.
+
+ * camel-service.c: removed the old hostent based hostname interfaces.
+
+ * camel-sasl-kerberos4.c (krb4_challenge): new hostname interfaces.
+
+ * camel-sasl-gssapi.c (gssapi_challenge): new hostname interfaces.
+
+ * camel-sasl-digest-md5.c (digest_md5_challenge): use new hostname
+ interfaces.
+ (generate_response): just take hostname directly, not hostent.
+
+ * camel-mime-utils.c (camel_header_msgid_generate): use new
+ hostname interfaces.
+
+ * providers/smtp/camel-smtp-transport.c (connect_to_server): fixed
+ to use new addrinfo apis.
+
+ * providers/pop3/camel-pop3-store.c (connect_to_server): fixed to
+ use new addrinfo apis.
+
+ * camel-tcp-stream-ssl.c (stream_connect): try all addresses
+ supplied.
+
+ * camel-tcp-stream.c (camel_tcp_stream_get_remote_address)
+ (camel_tcp_stream_get_local_address): return a sockaddr now, and
+ also the address length. Fixed all implementations and callers.
+ (camel_tcp_stream_connect): use addrinfo rather than hostent for
+ host AND port info. Fixed all implementations and callers.
+
2004-09-22 Not Zed <NotZed@Ximian.com>
* camel-folder-summary.c (camel_folder_summary_decode_token):
diff --git a/camel/camel-http-stream.c b/camel/camel-http-stream.c
index 5937c23f7..53c0241a2 100644
--- a/camel/camel-http-stream.c
+++ b/camel/camel-http-stream.c
@@ -172,9 +172,12 @@ static CamelStream *
http_connect (CamelHttpStream *http, CamelURL *url)
{
CamelStream *stream = NULL;
- struct hostent *host;
+ struct addrinfo *ai, hints = { 0 };
int errsave;
-
+ char *serv;
+
+ d(printf("connecting to http stream @ '%s'\n", url->host));
+
if (!strcasecmp (url->protocol, "https")) {
#ifdef HAVE_SSL
stream = camel_tcp_stream_ssl_new (http->session, url->host, SSL_FLAGS);
@@ -187,24 +190,30 @@ http_connect (CamelHttpStream *http, CamelURL *url)
errno = EINVAL;
return NULL;
}
-
- printf("connecting to http stream @ '%s'\n", url->host);
- host = camel_gethostbyname (url->host, NULL);
- if (!host) {
- errno = EHOSTUNREACH;
+ if (url->port) {
+ serv = g_alloca(16);
+ sprintf(serv, "%d", url->port);
+ } else {
+ serv = url->protocol;
+ }
+ hints.ai_socktype = SOCK_STREAM;
+
+ ai = camel_getaddrinfo(url->host, serv, &hints, NULL);
+ if (ai == NULL) {
+ camel_object_unref (stream);
return NULL;
}
- if (camel_tcp_stream_connect (CAMEL_TCP_STREAM (stream), host, url->port ? url->port : 80) == -1) {
+ if (camel_tcp_stream_connect (CAMEL_TCP_STREAM (stream), ai) == -1) {
errsave = errno;
camel_object_unref (stream);
- camel_free_host (host);
+ camel_freeaddrinfo(ai);
errno = errsave;
return NULL;
}
- camel_free_host (host);
+ camel_freeaddrinfo(ai);
http->raw = stream;
http->read = camel_stream_buffer_new (stream, CAMEL_STREAM_BUFFER_READ);
diff --git a/camel/camel-mime-utils.c b/camel/camel-mime-utils.c
new file mode 100644
index 000000000..3c1d4baf1
--- /dev/null
+++ b/camel/camel-mime-utils.c
@@ -0,0 +1,4314 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2000-2003 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+/* dont touch this file without my permission - Michael */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/param.h> /* for MAXHOSTNAMELEN */
+#include <sys/stat.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <regex.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <time.h>
+
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 1024
+#endif
+
+#include <glib.h>
+#include <gal/util/e-iconv.h>
+#include <e-util/e-time-utils.h>
+
+#include "camel-mime-utils.h"
+#include "camel-charset-map.h"
+#include "camel-service.h" /* for camel_gethostbyname() */
+#include "camel-utf8.h"
+
+#ifndef CLEAN_DATE
+#include "broken-date-parser.h"
+#endif
+
+#if 0
+int strdup_count = 0;
+int malloc_count = 0;
+int free_count = 0;
+
+#define g_strdup(x) (strdup_count++, g_strdup(x))
+#define g_malloc(x) (malloc_count++, g_malloc(x))
+#define g_free(x) (free_count++, g_free(x))
+#endif
+
+/* for all non-essential warnings ... */
+#define w(x)
+
+#define d(x)
+#define d2(x)
+
+#define CAMEL_UUENCODE_CHAR(c) ((c) ? (c) + ' ' : '`')
+#define CAMEL_UUDECODE_CHAR(c) (((c) - ' ') & 077)
+
+static char *base64_alphabet =
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static unsigned char tohex[16] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+};
+
+unsigned short camel_mime_special_table[256];
+static unsigned char camel_mime_base64_rank[256];
+
+/* Used by table initialisation code for special characters */
+#define CHARS_LWSP " \t\n\r"
+#define CHARS_TSPECIAL "()<>@,;:\\\"/[]?="
+#define CHARS_SPECIAL "()<>@,;:\\\".[]"
+#define CHARS_CSPECIAL "()\\\r" /* not in comments */
+#define CHARS_DSPECIAL "[]\\\r \t" /* not in domains */
+#define CHARS_ESPECIAL "()<>@,;:\"/[]?.=_" /* list of characters that must be encoded.
+ encoded word in text specials: rfc 2047 5(1)*/
+#define CHARS_PSPECIAL "!*+-/" /* list of additional characters that can be left unencoded.
+ encoded word in phrase specials: rfc 2047 5(3) */
+#define CHARS_ATTRCHAR "*\'% " /* extra non-included attribute-chars */
+
+static void
+header_remove_bits(unsigned short bit, unsigned char *vals)
+{
+ int i;
+
+ for (i=0;vals[i];i++)
+ camel_mime_special_table[vals[i]] &= ~ bit;
+}
+
+static void
+header_init_bits(unsigned short bit, unsigned short bitcopy, int remove, unsigned char *vals)
+{
+ int i;
+ int len = strlen(vals);
+
+ if (!remove) {
+ for (i=0;i<len;i++) {
+ camel_mime_special_table[vals[i]] |= bit;
+ }
+ if (bitcopy) {
+ for (i=0;i<256;i++) {
+ if (camel_mime_special_table[i] & bitcopy)
+ camel_mime_special_table[i] |= bit;
+ }
+ }
+ } else {
+ for (i=0;i<256;i++)
+ camel_mime_special_table[i] |= bit;
+ for (i=0;i<len;i++) {
+ camel_mime_special_table[vals[i]] &= ~bit;
+ }
+ if (bitcopy) {
+ for (i=0;i<256;i++) {
+ if (camel_mime_special_table[i] & bitcopy)
+ camel_mime_special_table[i] &= ~bit;
+ }
+ }
+ }
+}
+
+static void
+header_decode_init(void)
+{
+ int i;
+
+ for (i=0;i<256;i++) {
+ camel_mime_special_table[i] = 0;
+ if (i<32 || i==127)
+ camel_mime_special_table[i] |= CAMEL_MIME_IS_CTRL;
+ else if (i < 127)
+ camel_mime_special_table[i] |= CAMEL_MIME_IS_ATTRCHAR;
+ if ((i>=32 && i<=60) || (i>=62 && i<=126) || i==9)
+ camel_mime_special_table[i] |= (CAMEL_MIME_IS_QPSAFE|CAMEL_MIME_IS_ESAFE);
+ if ((i>='0' && i<='9') || (i>='a' && i<='z') || (i>='A' && i<= 'Z'))
+ camel_mime_special_table[i] |= CAMEL_MIME_IS_PSAFE;
+ }
+ camel_mime_special_table[' '] |= CAMEL_MIME_IS_SPACE;
+ header_init_bits(CAMEL_MIME_IS_LWSP, 0, 0, CHARS_LWSP);
+ header_init_bits(CAMEL_MIME_IS_TSPECIAL, CAMEL_MIME_IS_CTRL, 0, CHARS_TSPECIAL);
+ header_init_bits(CAMEL_MIME_IS_SPECIAL, 0, 0, CHARS_SPECIAL);
+ header_init_bits(CAMEL_MIME_IS_DSPECIAL, 0, FALSE, CHARS_DSPECIAL);
+ header_remove_bits(CAMEL_MIME_IS_ESAFE, CHARS_ESPECIAL);
+ header_remove_bits(CAMEL_MIME_IS_ATTRCHAR, CHARS_TSPECIAL CHARS_ATTRCHAR);
+ header_init_bits(CAMEL_MIME_IS_PSAFE, 0, 0, CHARS_PSPECIAL);
+}
+
+static void
+base64_init(void)
+{
+ int i;
+
+ memset(camel_mime_base64_rank, 0xff, sizeof(camel_mime_base64_rank));
+ for (i=0;i<64;i++) {
+ camel_mime_base64_rank[(unsigned int)base64_alphabet[i]] = i;
+ }
+ camel_mime_base64_rank['='] = 0;
+}
+
+/* call this when finished encoding everything, to
+ flush off the last little bit */
+size_t
+camel_base64_encode_close(unsigned char *in, size_t inlen, gboolean break_lines, unsigned char *out, int *state, int *save)
+{
+ int c1, c2;
+ unsigned char *outptr = out;
+
+ if (inlen>0)
+ outptr += camel_base64_encode_step(in, inlen, break_lines, outptr, state, save);
+
+ c1 = ((unsigned char *)save)[1];
+ c2 = ((unsigned char *)save)[2];
+
+ d(printf("mode = %d\nc1 = %c\nc2 = %c\n",
+ (int)((char *)save)[0],
+ (int)((char *)save)[1],
+ (int)((char *)save)[2]));
+
+ switch (((char *)save)[0]) {
+ case 2:
+ outptr[2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ];
+ g_assert(outptr[2] != 0);
+ goto skip;
+ case 1:
+ outptr[2] = '=';
+ skip:
+ outptr[0] = base64_alphabet[ c1 >> 2 ];
+ outptr[1] = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 )];
+ outptr[3] = '=';
+ outptr += 4;
+ break;
+ }
+ if (break_lines)
+ *outptr++ = '\n';
+
+ *save = 0;
+ *state = 0;
+
+ return outptr-out;
+}
+
+/*
+ performs an 'encode step', only encodes blocks of 3 characters to the
+ output at a time, saves left-over state in state and save (initialise to
+ 0 on first invocation).
+*/
+size_t
+camel_base64_encode_step(unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save)
+{
+ register unsigned char *inptr, *outptr;
+
+ if (len<=0)
+ return 0;
+
+ inptr = in;
+ outptr = out;
+
+ d(printf("we have %d chars, and %d saved chars\n", len, ((char *)save)[0]));
+
+ if (len + ((char *)save)[0] > 2) {
+ unsigned char *inend = in+len-2;
+ register int c1, c2, c3;
+ register int already;
+
+ already = *state;
+
+ switch (((char *)save)[0]) {
+ case 1: c1 = ((unsigned char *)save)[1]; goto skip1;
+ case 2: c1 = ((unsigned char *)save)[1];
+ c2 = ((unsigned char *)save)[2]; goto skip2;
+ }
+
+ /* yes, we jump into the loop, no i'm not going to change it, it's beautiful! */
+ while (inptr < inend) {
+ c1 = *inptr++;
+ skip1:
+ c2 = *inptr++;
+ skip2:
+ c3 = *inptr++;
+ *outptr++ = base64_alphabet[ c1 >> 2 ];
+ *outptr++ = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 ) ];
+ *outptr++ = base64_alphabet[ ( (c2 &0x0f) << 2 ) | (c3 >> 6) ];
+ *outptr++ = base64_alphabet[ c3 & 0x3f ];
+ /* this is a bit ugly ... */
+ if (break_lines && (++already)>=19) {
+ *outptr++='\n';
+ already = 0;
+ }
+ }
+
+ ((char *)save)[0] = 0;
+ len = 2-(inptr-inend);
+ *state = already;
+ }
+
+ d(printf("state = %d, len = %d\n",
+ (int)((char *)save)[0],
+ len));
+
+ if (len>0) {
+ register char *saveout;
+
+ /* points to the slot for the next char to save */
+ saveout = & (((char *)save)[1]) + ((char *)save)[0];
+
+ /* len can only be 0 1 or 2 */
+ switch(len) {
+ case 2: *saveout++ = *inptr++;
+ case 1: *saveout++ = *inptr++;
+ }
+ ((char *)save)[0]+=len;
+ }
+
+ d(printf("mode = %d\nc1 = %c\nc2 = %c\n",
+ (int)((char *)save)[0],
+ (int)((char *)save)[1],
+ (int)((char *)save)[2]));
+
+ return outptr-out;
+}
+
+
+/**
+ * camel_base64_decode_step: decode a chunk of base64 encoded data
+ * @in: input stream
+ * @len: max length of data to decode
+ * @out: output stream
+ * @state: holds the number of bits that are stored in @save
+ * @save: leftover bits that have not yet been decoded
+ *
+ * Decodes a chunk of base64 encoded data
+ **/
+size_t
+camel_base64_decode_step(unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save)
+{
+ register unsigned char *inptr, *outptr;
+ unsigned char *inend, c;
+ register unsigned int v;
+ int i;
+
+ inend = in+len;
+ outptr = out;
+
+ /* convert 4 base64 bytes to 3 normal bytes */
+ v=*save;
+ i=*state;
+ inptr = in;
+ while (inptr<inend) {
+ c = camel_mime_base64_rank[*inptr++];
+ if (c != 0xff) {
+ v = (v<<6) | c;
+ i++;
+ if (i==4) {
+ *outptr++ = v>>16;
+ *outptr++ = v>>8;
+ *outptr++ = v;
+ i=0;
+ }
+ }
+ }
+
+ *save = v;
+ *state = i;
+
+ /* quick scan back for '=' on the end somewhere */
+ /* fortunately we can drop 1 output char for each trailing = (upto 2) */
+ i=2;
+ while (inptr>in && i) {
+ inptr--;
+ if (camel_mime_base64_rank[*inptr] != 0xff) {
+ if (*inptr == '=' && outptr>out)
+ outptr--;
+ i--;
+ }
+ }
+
+ /* if i!= 0 then there is a truncation error! */
+ return outptr-out;
+}
+
+char *
+camel_base64_encode_simple (const char *data, size_t len)
+{
+ unsigned char *out;
+ int state = 0, outlen;
+ unsigned int save = 0;
+
+ out = g_malloc (len * 4 / 3 + 5);
+ outlen = camel_base64_encode_close ((unsigned char *)data, len, FALSE,
+ out, &state, &save);
+ out[outlen] = '\0';
+ return (char *)out;
+}
+
+size_t
+camel_base64_decode_simple (char *data, size_t len)
+{
+ int state = 0;
+ unsigned int save = 0;
+
+ return camel_base64_decode_step ((unsigned char *)data, len,
+ (unsigned char *)data, &state, &save);
+}
+
+/**
+ * camel_uuencode_close: uuencode a chunk of data
+ * @in: input stream
+ * @len: input stream length
+ * @out: output stream
+ * @uubuf: temporary buffer of 60 bytes
+ * @state: holds the number of bits that are stored in @save
+ * @save: leftover bits that have not yet been encoded
+ *
+ * Returns the number of bytes encoded. Call this when finished
+ * encoding data with camel_uuencode_step to flush off the last little
+ * bit.
+ **/
+size_t
+camel_uuencode_close (unsigned char *in, size_t len, unsigned char *out, unsigned char *uubuf, int *state, guint32 *save)
+{
+ register unsigned char *outptr, *bufptr;
+ register guint32 saved;
+ int uulen, uufill, i;
+
+ outptr = out;
+
+ if (len > 0)
+ outptr += camel_uuencode_step (in, len, out, uubuf, state, save);
+
+ uufill = 0;
+
+ saved = *save;
+ i = *state & 0xff;
+ uulen = (*state >> 8) & 0xff;
+
+ bufptr = uubuf + ((uulen / 3) * 4);
+
+ if (i > 0) {
+ while (i < 3) {
+ saved <<= 8 | 0;
+ uufill++;
+ i++;
+ }
+
+ if (i == 3) {
+ /* convert 3 normal bytes into 4 uuencoded bytes */
+ unsigned char b0, b1, b2;
+
+ b0 = saved >> 16;
+ b1 = saved >> 8 & 0xff;
+ b2 = saved & 0xff;
+
+ *bufptr++ = CAMEL_UUENCODE_CHAR ((b0 >> 2) & 0x3f);
+ *bufptr++ = CAMEL_UUENCODE_CHAR (((b0 << 4) | ((b1 >> 4) & 0xf)) & 0x3f);
+ *bufptr++ = CAMEL_UUENCODE_CHAR (((b1 << 2) | ((b2 >> 6) & 0x3)) & 0x3f);
+ *bufptr++ = CAMEL_UUENCODE_CHAR (b2 & 0x3f);
+
+ i = 0;
+ saved = 0;
+ uulen += 3;
+ }
+ }
+
+ if (uulen > 0) {
+ int cplen = ((uulen / 3) * 4);
+
+ *outptr++ = CAMEL_UUENCODE_CHAR ((uulen - uufill) & 0xff);
+ memcpy (outptr, uubuf, cplen);
+ outptr += cplen;
+ *outptr++ = '\n';
+ uulen = 0;
+ }
+
+ *outptr++ = CAMEL_UUENCODE_CHAR (uulen & 0xff);
+ *outptr++ = '\n';
+
+ *save = 0;
+ *state = 0;
+
+ return outptr - out;
+}
+
+
+/**
+ * camel_uuencode_step: uuencode a chunk of data
+ * @in: input stream
+ * @len: input stream length
+ * @out: output stream
+ * @uubuf: temporary buffer of 60 bytes
+ * @state: holds the number of bits that are stored in @save
+ * @save: leftover bits that have not yet been encoded
+ *
+ * Returns the number of bytes encoded. Performs an 'encode step',
+ * only encodes blocks of 45 characters to the output at a time, saves
+ * left-over state in @uubuf, @state and @save (initialize to 0 on first
+ * invocation).
+ **/
+size_t
+camel_uuencode_step (unsigned char *in, size_t len, unsigned char *out, unsigned char *uubuf, int *state, guint32 *save)
+{
+ register unsigned char *inptr, *outptr, *bufptr;
+ unsigned char *inend;
+ register guint32 saved;
+ int uulen, i;
+
+ saved = *save;
+ i = *state & 0xff;
+ uulen = (*state >> 8) & 0xff;
+
+ inptr = in;
+ inend = in + len;
+
+ outptr = out;
+
+ bufptr = uubuf + ((uulen / 3) * 4);
+
+ while (inptr < inend) {
+ while (uulen < 45 && inptr < inend) {
+ while (i < 3 && inptr < inend) {
+ saved = (saved << 8) | *inptr++;
+ i++;
+ }
+
+ if (i == 3) {
+ /* convert 3 normal bytes into 4 uuencoded bytes */
+ unsigned char b0, b1, b2;
+
+ b0 = saved >> 16;
+ b1 = saved >> 8 & 0xff;
+ b2 = saved & 0xff;
+
+ *bufptr++ = CAMEL_UUENCODE_CHAR ((b0 >> 2) & 0x3f);
+ *bufptr++ = CAMEL_UUENCODE_CHAR (((b0 << 4) | ((b1 >> 4) & 0xf)) & 0x3f);
+ *bufptr++ = CAMEL_UUENCODE_CHAR (((b1 << 2) | ((b2 >> 6) & 0x3)) & 0x3f);
+ *bufptr++ = CAMEL_UUENCODE_CHAR (b2 & 0x3f);
+
+ i = 0;
+ saved = 0;
+ uulen += 3;
+ }
+ }
+
+ if (uulen >= 45) {
+ *outptr++ = CAMEL_UUENCODE_CHAR (uulen & 0xff);
+ memcpy (outptr, uubuf, ((uulen / 3) * 4));
+ outptr += ((uulen / 3) * 4);
+ *outptr++ = '\n';
+ uulen = 0;
+ bufptr = uubuf;
+ }
+ }
+
+ *save = saved;
+ *state = ((uulen & 0xff) << 8) | (i & 0xff);
+
+ return outptr - out;
+}
+
+
+/**
+ * camel_uudecode_step: uudecode a chunk of data
+ * @in: input stream
+ * @inlen: max length of data to decode ( normally strlen(in) ??)
+ * @out: output stream
+ * @state: holds the number of bits that are stored in @save
+ * @save: leftover bits that have not yet been decoded
+ *
+ * Returns the number of bytes decoded. Performs a 'decode step' on
+ * a chunk of uuencoded data. Assumes the "begin <mode> <file name>"
+ * line has been stripped off.
+ **/
+size_t
+camel_uudecode_step (unsigned char *in, size_t len, unsigned char *out, int *state, guint32 *save)
+{
+ register unsigned char *inptr, *outptr;
+ unsigned char *inend, ch;
+ register guint32 saved;
+ gboolean last_was_eoln;
+ int uulen, i;
+
+ if (*state & CAMEL_UUDECODE_STATE_END)
+ return 0;
+
+ saved = *save;
+ i = *state & 0xff;
+ uulen = (*state >> 8) & 0xff;
+ if (uulen == 0)
+ last_was_eoln = TRUE;
+ else
+ last_was_eoln = FALSE;
+
+ inend = in + len;
+ outptr = out;
+
+ inptr = in;
+ while (inptr < inend) {
+ if (*inptr == '\n' || last_was_eoln) {
+ if (last_was_eoln && *inptr != '\n') {
+ uulen = CAMEL_UUDECODE_CHAR (*inptr);
+ last_was_eoln = FALSE;
+ if (uulen == 0) {
+ *state |= CAMEL_UUDECODE_STATE_END;
+ break;
+ }
+ } else {
+ last_was_eoln = TRUE;
+ }
+
+ inptr++;
+ continue;
+ }
+
+ ch = *inptr++;
+
+ if (uulen > 0) {
+ /* save the byte */
+ saved = (saved << 8) | ch;
+ i++;
+ if (i == 4) {
+ /* convert 4 uuencoded bytes to 3 normal bytes */
+ unsigned char b0, b1, b2, b3;
+
+ b0 = saved >> 24;
+ b1 = saved >> 16 & 0xff;
+ b2 = saved >> 8 & 0xff;
+ b3 = saved & 0xff;
+
+ if (uulen >= 3) {
+ *outptr++ = CAMEL_UUDECODE_CHAR (b0) << 2 | CAMEL_UUDECODE_CHAR (b1) >> 4;
+ *outptr++ = CAMEL_UUDECODE_CHAR (b1) << 4 | CAMEL_UUDECODE_CHAR (b2) >> 2;
+ *outptr++ = CAMEL_UUDECODE_CHAR (b2) << 6 | CAMEL_UUDECODE_CHAR (b3);
+ } else {
+ if (uulen >= 1) {
+ *outptr++ = CAMEL_UUDECODE_CHAR (b0) << 2 | CAMEL_UUDECODE_CHAR (b1) >> 4;
+ }
+ if (uulen >= 2) {
+ *outptr++ = CAMEL_UUDECODE_CHAR (b1) << 4 | CAMEL_UUDECODE_CHAR (b2) >> 2;
+ }
+ }
+
+ i = 0;
+ saved = 0;
+ uulen -= 3;
+ }
+ } else {
+ break;
+ }
+ }
+
+ *save = saved;
+ *state = (*state & CAMEL_UUDECODE_STATE_MASK) | ((uulen & 0xff) << 8) | (i & 0xff);
+
+ return outptr - out;
+}
+
+
+/* complete qp encoding */
+size_t
+camel_quoted_decode_close(unsigned char *in, size_t len, unsigned char *out, int *state, int *save)
+{
+ register unsigned char *outptr = out;
+ int last;
+
+ if (len>0)
+ outptr += camel_quoted_encode_step(in, len, outptr, state, save);
+
+ last = *state;
+ if (last != -1) {
+ /* space/tab must be encoded if it's the last character on
+ the line */
+ if (camel_mime_is_qpsafe(last) && last!=' ' && last!=9) {
+ *outptr++ = last;
+ } else {
+ *outptr++ = '=';
+ *outptr++ = tohex[(last>>4) & 0xf];
+ *outptr++ = tohex[last & 0xf];
+ }
+ }
+
+ *save = 0;
+ *state = -1;
+
+ return outptr-out;
+}
+
+/* perform qp encoding, initialise state to -1 and save to 0 on first invocation */
+size_t
+camel_quoted_encode_step (unsigned char *in, size_t len, unsigned char *out, int *statep, int *save)
+{
+ register guchar *inptr, *outptr, *inend;
+ unsigned char c;
+ register int sofar = *save; /* keeps track of how many chars on a line */
+ register int last = *statep; /* keeps track if last char to end was a space cr etc */
+
+ inptr = in;
+ inend = in + len;
+ outptr = out;
+ while (inptr < inend) {
+ c = *inptr++;
+ if (c == '\r') {
+ if (last != -1) {
+ *outptr++ = '=';
+ *outptr++ = tohex[(last >> 4) & 0xf];
+ *outptr++ = tohex[last & 0xf];
+ sofar += 3;
+ }
+ last = c;
+ } else if (c == '\n') {
+ if (last != -1 && last != '\r') {
+ *outptr++ = '=';
+ *outptr++ = tohex[(last >> 4) & 0xf];
+ *outptr++ = tohex[last & 0xf];
+ }
+ *outptr++ = '\n';
+ sofar = 0;
+ last = -1;
+ } else {
+ if (last != -1) {
+ if (camel_mime_is_qpsafe(last)) {
+ *outptr++ = last;
+ sofar++;
+ } else {
+ *outptr++ = '=';
+ *outptr++ = tohex[(last >> 4) & 0xf];
+ *outptr++ = tohex[last & 0xf];
+ sofar += 3;
+ }
+ }
+
+ if (camel_mime_is_qpsafe(c)) {
+ if (sofar > 74) {
+ *outptr++ = '=';
+ *outptr++ = '\n';
+ sofar = 0;
+ }
+
+ /* delay output of space char */
+ if (c==' ' || c=='\t') {
+ last = c;
+ } else {
+ *outptr++ = c;
+ sofar++;
+ last = -1;
+ }
+ } else {
+ if (sofar > 72) {
+ *outptr++ = '=';
+ *outptr++ = '\n';
+ sofar = 3;
+ } else
+ sofar += 3;
+
+ *outptr++ = '=';
+ *outptr++ = tohex[(c >> 4) & 0xf];
+ *outptr++ = tohex[c & 0xf];
+ last = -1;
+ }
+ }
+ }
+ *save = sofar;
+ *statep = last;
+
+ return (outptr - out);
+}
+
+/*
+ FIXME: this does not strip trailing spaces from lines (as it should, rfc 2045, section 6.7)
+ Should it also canonicalise the end of line to CR LF??
+
+ Note: Trailing rubbish (at the end of input), like = or =x or =\r will be lost.
+*/
+
+size_t
+camel_quoted_decode_step(unsigned char *in, size_t len, unsigned char *out, int *savestate, int *saveme)
+{
+ register unsigned char *inptr, *outptr;
+ unsigned char *inend, c;
+ int state, save;
+
+ inend = in+len;
+ outptr = out;
+
+ d(printf("quoted-printable, decoding text '%.*s'\n", len, in));
+
+ state = *savestate;
+ save = *saveme;
+ inptr = in;
+ while (inptr<inend) {
+ switch (state) {
+ case 0:
+ while (inptr<inend) {
+ c = *inptr++;
+ if (c=='=') {
+ state = 1;
+ break;
+ }
+#ifdef CANONICALISE_EOL
+ /*else if (c=='\r') {
+ state = 3;
+ } else if (c=='\n') {
+ *outptr++ = '\r';
+ *outptr++ = c;
+ } */
+#endif
+ else {
+ *outptr++ = c;
+ }
+ }
+ break;
+ case 1:
+ c = *inptr++;
+ if (c=='\n') {
+ /* soft break ... unix end of line */
+ state = 0;
+ } else {
+ save = c;
+ state = 2;
+ }
+ break;
+ case 2:
+ c = *inptr++;
+ if (isxdigit(c) && isxdigit(save)) {
+ c = toupper(c);
+ save = toupper(save);
+ *outptr++ = (((save>='A'?save-'A'+10:save-'0')&0x0f) << 4)
+ | ((c>='A'?c-'A'+10:c-'0')&0x0f);
+ } else if (c=='\n' && save == '\r') {
+ /* soft break ... canonical end of line */
+ } else {
+ /* just output the data */
+ *outptr++ = '=';
+ *outptr++ = save;
+ *outptr++ = c;
+ }
+ state = 0;
+ break;
+#ifdef CANONICALISE_EOL
+ case 3:
+ /* convert \r -> to \r\n, leaves \r\n alone */
+ c = *inptr++;
+ if (c=='\n') {
+ *outptr++ = '\r';
+ *outptr++ = c;
+ } else {
+ *outptr++ = '\r';
+ *outptr++ = '\n';
+ *outptr++ = c;
+ }
+ state = 0;
+ break;
+#endif
+ }
+ }
+
+ *savestate = state;
+ *saveme = save;
+
+ return outptr-out;
+}
+
+/*
+ this is for the "Q" encoding of international words,
+ which is slightly different than plain quoted-printable (mainly by allowing 0x20 <> _)
+*/
+static size_t
+quoted_decode(const unsigned char *in, size_t len, unsigned char *out)
+{
+ register const unsigned char *inptr;
+ register unsigned char *outptr;
+ unsigned const char *inend;
+ unsigned char c, c1;
+ int ret = 0;
+
+ inend = in+len;
+ outptr = out;
+
+ d(printf("decoding text '%.*s'\n", len, in));
+
+ inptr = in;
+ while (inptr<inend) {
+ c = *inptr++;
+ if (c=='=') {
+ /* silently ignore truncated data? */
+ if (inend-in>=2) {
+ c = toupper(*inptr++);
+ c1 = toupper(*inptr++);
+ *outptr++ = (((c>='A'?c-'A'+10:c-'0')&0x0f) << 4)
+ | ((c1>='A'?c1-'A'+10:c1-'0')&0x0f);
+ } else {
+ ret = -1;
+ break;
+ }
+ } else if (c=='_') {
+ *outptr++ = 0x20;
+ } else if (c==' ' || c==0x09) {
+ /* FIXME: this is an error! ignore for now ... */
+ ret = -1;
+ break;
+ } else {
+ *outptr++ = c;
+ }
+ }
+ if (ret==0) {
+ return outptr-out;
+ }
+ return 0;
+}
+
+/* rfc2047 version of quoted-printable */
+/* safemask is the mask to apply to the camel_mime_special_table to determine what
+ characters can safely be included without encoding */
+static size_t
+quoted_encode (const unsigned char *in, size_t len, unsigned char *out, unsigned short safemask)
+{
+ register const unsigned char *inptr, *inend;
+ unsigned char *outptr;
+ unsigned char c;
+
+ inptr = in;
+ inend = in + len;
+ outptr = out;
+ while (inptr < inend) {
+ c = *inptr++;
+ if (c==' ') {
+ *outptr++ = '_';
+ } else if (camel_mime_special_table[c] & safemask) {
+ *outptr++ = c;
+ } else {
+ *outptr++ = '=';
+ *outptr++ = tohex[(c >> 4) & 0xf];
+ *outptr++ = tohex[c & 0xf];
+ }
+ }
+
+ d(printf("encoding '%.*s' = '%.*s'\n", len, in, outptr-out, out));
+
+ return (outptr - out);
+}
+
+
+static void
+header_decode_lwsp(const char **in)
+{
+ const char *inptr = *in;
+ char c;
+
+ d2(printf("is ws: '%s'\n", *in));
+
+ while (camel_mime_is_lwsp(*inptr) || (*inptr =='(' && *inptr != '\0')) {
+ while (camel_mime_is_lwsp(*inptr) && inptr != '\0') {
+ d2(printf("(%c)", *inptr));
+ inptr++;
+ }
+ d2(printf("\n"));
+
+ /* check for comments */
+ if (*inptr == '(') {
+ int depth = 1;
+ inptr++;
+ while (depth && (c=*inptr) && *inptr != '\0') {
+ if (c=='\\' && inptr[1]) {
+ inptr++;
+ } else if (c=='(') {
+ depth++;
+ } else if (c==')') {
+ depth--;
+ }
+ inptr++;
+ }
+ }
+ }
+ *in = inptr;
+}
+
+/* decode rfc 2047 encoded string segment */
+static char *
+rfc2047_decode_word(const char *in, size_t len)
+{
+ const char *inptr = in+2;
+ const char *inend = in+len-2;
+ const char *inbuf;
+ const char *charset;
+ char *encname, *p;
+ int tmplen;
+ size_t ret;
+ char *decword = NULL;
+ char *decoded = NULL;
+ char *outbase = NULL;
+ char *outbuf;
+ size_t inlen, outlen;
+ gboolean retried = FALSE;
+ iconv_t ic;
+
+ d(printf("rfc2047: decoding '%.*s'\n", len, in));
+
+ /* quick check to see if this could possibly be a real encoded word */
+ if (len < 8 || !(in[0] == '=' && in[1] == '?' && in[len-1] == '=' && in[len-2] == '?')) {
+ d(printf("invalid\n"));
+ return NULL;
+ }
+
+ /* skip past the charset to the encoding type */
+ inptr = memchr (inptr, '?', inend-inptr);
+ if (inptr != NULL && inptr < inend + 2 && inptr[2] == '?') {
+ d(printf("found ?, encoding is '%c'\n", inptr[0]));
+ inptr++;
+ tmplen = inend-inptr-2;
+ decword = g_alloca (tmplen); /* this will always be more-than-enough room */
+ switch(toupper(inptr[0])) {
+ case 'Q':
+ inlen = quoted_decode(inptr+2, tmplen, decword);
+ break;
+ case 'B': {
+ int state = 0;
+ unsigned int save = 0;
+
+ inlen = camel_base64_decode_step((char *)inptr+2, tmplen, decword, &state, &save);
+ /* if state != 0 then error? */
+ break;
+ }
+ default:
+ /* uhhh, unknown encoding type - probably an invalid encoded word string */
+ return NULL;
+ }
+ d(printf("The encoded length = %d\n", inlen));
+ if (inlen > 0) {
+ /* yuck, all this snot is to setup iconv! */
+ tmplen = inptr - in - 3;
+ encname = g_alloca (tmplen + 1);
+ memcpy (encname, in + 2, tmplen);
+ encname[tmplen] = '\0';
+
+ /* rfc2231 updates rfc2047 encoded words...
+ * The ABNF given in RFC 2047 for encoded-words is:
+ * encoded-word := "=?" charset "?" encoding "?" encoded-text "?="
+ * This specification changes this ABNF to:
+ * encoded-word := "=?" charset ["*" language] "?" encoding "?" encoded-text "?="
+ */
+
+ /* trim off the 'language' part if it's there... */
+ p = strchr (encname, '*');
+ if (p)
+ *p = '\0';
+
+ charset = e_iconv_charset_name (encname);
+
+ inbuf = decword;
+
+ outlen = inlen * 6 + 16;
+ outbase = g_alloca (outlen);
+ outbuf = outbase;
+
+ retry:
+ ic = e_iconv_open ("UTF-8", charset);
+ if (ic != (iconv_t) -1) {
+ ret = e_iconv (ic, &inbuf, &inlen, &outbuf, &outlen);
+ if (ret != (size_t) -1) {
+ e_iconv (ic, NULL, 0, &outbuf, &outlen);
+ *outbuf = 0;
+ decoded = g_strdup (outbase);
+ }
+ e_iconv_close (ic);
+ } else {
+ w(g_warning ("Cannot decode charset, header display may be corrupt: %s: %s",
+ charset, strerror (errno)));
+
+ if (!retried) {
+ charset = e_iconv_locale_charset ();
+ if (!charset)
+ charset = "iso-8859-1";
+
+ retried = TRUE;
+ goto retry;
+ }
+
+ /* we return the encoded word here because we've got to return valid utf8 */
+ decoded = g_strndup (in, inlen);
+ }
+ }
+ }
+
+ d(printf("decoded '%s'\n", decoded));
+
+ return decoded;
+}
+
+/* ok, a lot of mailers are BROKEN, and send iso-latin1 encoded
+ headers, when they should just be sticking to US-ASCII
+ according to the rfc's. Anyway, since the conversion to utf-8
+ is trivial, just do it here without iconv */
+static GString *
+append_latin1 (GString *out, const char *in, size_t len)
+{
+ unsigned int c;
+
+ while (len) {
+ c = (unsigned int)*in++;
+ len--;
+ if (c & 0x80) {
+ out = g_string_append_c (out, 0xc0 | ((c >> 6) & 0x3)); /* 110000xx */
+ out = g_string_append_c (out, 0x80 | (c & 0x3f)); /* 10xxxxxx */
+ } else {
+ out = g_string_append_c (out, c);
+ }
+ }
+ return out;
+}
+
+static int
+append_8bit (GString *out, const char *inbuf, size_t inlen, const char *charset)
+{
+ char *outbase, *outbuf;
+ size_t outlen;
+ iconv_t ic;
+
+ ic = e_iconv_open ("UTF-8", charset);
+ if (ic == (iconv_t) -1)
+ return FALSE;
+
+ outlen = inlen * 6 + 16;
+ outbuf = outbase = g_malloc(outlen);
+
+ if (e_iconv (ic, &inbuf, &inlen, &outbuf, &outlen) == (size_t) -1) {
+ w(g_warning("Conversion to '%s' failed: %s", charset, strerror (errno)));
+ g_free(outbase);
+ e_iconv_close (ic);
+ return FALSE;
+ }
+
+ e_iconv (ic, NULL, NULL, &outbuf, &outlen);
+
+ *outbuf = 0;
+ g_string_append(out, outbase);
+ g_free(outbase);
+ e_iconv_close (ic);
+
+ return TRUE;
+
+}
+
+static void
+append_quoted_pair (GString *str, const char *in, int inlen)
+{
+ register const char *inptr = in;
+ const char *inend = in + inlen;
+ char c;
+
+ while (inptr < inend) {
+ c = *inptr++;
+ if (c == '\\' && inptr < inend)
+ g_string_append_c (str, *inptr++);
+ else
+ g_string_append_c (str, c);
+ }
+}
+
+/* decodes a simple text, rfc822 + rfc2047 */
+static char *
+header_decode_text (const char *in, size_t inlen, int ctext, const char *default_charset)
+{
+ GString *out;
+ const char *inptr, *inend, *start, *chunk, *locale_charset;
+ void (* append) (GString *, const char *, int);
+ char *dword = NULL;
+ guint32 mask;
+
+ locale_charset = e_iconv_locale_charset ();
+
+ if (ctext) {
+ mask = (CAMEL_MIME_IS_SPECIAL | CAMEL_MIME_IS_SPACE | CAMEL_MIME_IS_CTRL);
+ append = append_quoted_pair;
+ } else {
+ mask = (CAMEL_MIME_IS_LWSP);
+ append = g_string_append_len;
+ }
+
+ out = g_string_new ("");
+ inptr = in;
+ inend = inptr + inlen;
+ chunk = NULL;
+
+ while (inptr < inend) {
+ start = inptr;
+ while (inptr < inend && camel_mime_is_type (*inptr, mask))
+ inptr++;
+
+ if (inptr == inend) {
+ append (out, start, inptr - start);
+ break;
+ } else if (dword == NULL) {
+ append (out, start, inptr - start);
+ } else {
+ chunk = start;
+ }
+
+ start = inptr;
+ while (inptr < inend && !camel_mime_is_type (*inptr, mask))
+ inptr++;
+
+ dword = rfc2047_decode_word(start, inptr-start);
+ if (dword) {
+ g_string_append(out, dword);
+ g_free(dword);
+ } else {
+ if (!chunk)
+ chunk = start;
+
+ if ((default_charset == NULL || !append_8bit (out, chunk, inptr-chunk, default_charset))
+ && (locale_charset == NULL || !append_8bit(out, chunk, inptr-chunk, locale_charset)))
+ append_latin1(out, chunk, inptr-chunk);
+ }
+
+ chunk = NULL;
+ }
+
+ dword = out->str;
+ g_string_free (out, FALSE);
+
+ return dword;
+}
+
+char *
+camel_header_decode_string (const char *in, const char *default_charset)
+{
+ if (in == NULL)
+ return NULL;
+ return header_decode_text (in, strlen (in), FALSE, default_charset);
+}
+
+char *
+camel_header_format_ctext (const char *in, const char *default_charset)
+{
+ if (in == NULL)
+ return NULL;
+ return header_decode_text (in, strlen (in), TRUE, default_charset);
+}
+
+/* how long a sequence of pre-encoded words should be less than, to attempt to
+ fit into a properly folded word. Only a guide. */
+#define CAMEL_FOLD_PREENCODED (24)
+
+/* FIXME: needs a way to cache iconv opens for different charsets? */
+static void
+rfc2047_encode_word(GString *outstring, const char *in, size_t len, const char *type, unsigned short safemask)
+{
+ iconv_t ic = (iconv_t) -1;
+ char *buffer, *out, *ascii;
+ size_t inlen, outlen, enclen, bufflen;
+ const char *inptr, *p;
+ int first = 1;
+
+ d(printf("Converting [%d] '%.*s' to %s\n", len, len, in, type));
+
+ /* convert utf8->encoding */
+ bufflen = len * 6 + 16;
+ buffer = g_alloca (bufflen);
+ inlen = len;
+ inptr = in;
+
+ ascii = g_alloca (bufflen);
+
+ if (strcasecmp (type, "UTF-8") != 0)
+ ic = e_iconv_open (type, "UTF-8");
+
+ while (inlen) {
+ size_t convlen, proclen;
+ int i;
+
+ /* break up words into smaller bits, what we really want is encoded + overhead < 75,
+ but we'll just guess what that means in terms of input chars, and assume its good enough */
+
+ out = buffer;
+ outlen = bufflen;
+
+ if (ic == (iconv_t) -1) {
+ /* native encoding case, the easy one (?) */
+ /* we work out how much we can convert, and still be in length */
+ /* proclen will be the result of input characters that we can convert, to the nearest
+ (approximated) valid utf8 char */
+ convlen = 0;
+ proclen = 0;
+ p = inptr;
+ i = 0;
+ while (p < (in+len) && convlen < (75 - strlen("=?utf-8?q\?\?="))) {
+ unsigned char c = *p++;
+
+ if (c >= 0xc0)
+ proclen = i;
+ i++;
+ if (c < 0x80)
+ proclen = i;
+ if (camel_mime_special_table[c] & safemask)
+ convlen += 1;
+ else
+ convlen += 3;
+ }
+ /* well, we probably have broken utf8, just copy it anyway what the heck */
+ if (proclen == 0) {
+ w(g_warning("Appear to have truncated utf8 sequence"));
+ proclen = inlen;
+ }
+ memcpy(out, inptr, proclen);
+ inptr += proclen;
+ inlen -= proclen;
+ out += proclen;
+ } else {
+ /* well we could do similar, but we can't (without undue effort), we'll just break it up into
+ hopefully-small-enough chunks, and leave it at that */
+ convlen = MIN(inlen, CAMEL_FOLD_PREENCODED);
+ p = inptr;
+ if (e_iconv (ic, &inptr, &convlen, &out, &outlen) == (size_t) -1 && errno != EINVAL) {
+ w(g_warning("Conversion problem: conversion truncated: %s", strerror (errno)));
+ /* blah, we include it anyway, better than infinite loop ... */
+ inptr = p + convlen;
+ } else {
+ /* make sure we flush out any shift state */
+ e_iconv (ic, NULL, 0, &out, &outlen);
+ }
+ inlen -= (inptr - p);
+ }
+
+ enclen = out-buffer;
+
+ if (enclen) {
+ /* create token */
+ out = ascii;
+ if (first)
+ first = 0;
+ else
+ *out++ = ' ';
+ out += sprintf (out, "=?%s?Q?", type);
+ out += quoted_encode (buffer, enclen, out, safemask);
+ sprintf (out, "?=");
+
+ d(printf("converted part = %s\n", ascii));
+
+ g_string_append (outstring, ascii);
+ }
+ }
+
+ if (ic != (iconv_t) -1)
+ e_iconv_close (ic);
+}
+
+
+/* TODO: Should this worry about quotes?? */
+char *
+camel_header_encode_string (const unsigned char *in)
+{
+ const unsigned char *inptr = in, *start, *word;
+ gboolean last_was_encoded = FALSE;
+ gboolean last_was_space = FALSE;
+ int encoding;
+ GString *out;
+ char *outstr;
+
+ g_return_val_if_fail (g_utf8_validate (in, -1, NULL), NULL);
+
+ if (in == NULL)
+ return NULL;
+
+ /* do a quick us-ascii check (the common case?) */
+ while (*inptr) {
+ if (*inptr > 127)
+ break;
+ inptr++;
+ }
+ if (*inptr == '\0')
+ return g_strdup (in);
+
+ /* This gets each word out of the input, and checks to see what charset
+ can be used to encode it. */
+ /* TODO: Work out when to merge subsequent words, or across word-parts */
+ out = g_string_new ("");
+ inptr = in;
+ encoding = 0;
+ word = NULL;
+ start = inptr;
+ while (inptr && *inptr) {
+ gunichar c;
+ const char *newinptr;
+
+ newinptr = g_utf8_next_char (inptr);
+ c = g_utf8_get_char (inptr);
+ if (newinptr == NULL || !g_unichar_validate (c)) {
+ w(g_warning ("Invalid UTF-8 sequence encountered (pos %d, char '%c'): %s",
+ (inptr-in), inptr[0], in));
+ inptr++;
+ continue;
+ }
+
+ if (c < 256 && camel_mime_is_lwsp (c) && !last_was_space) {
+ /* we've reached the end of a 'word' */
+ if (word && !(last_was_encoded && encoding)) {
+ /* output lwsp between non-encoded words */
+ g_string_append_len (out, start, word - start);
+ start = word;
+ }
+
+ switch (encoding) {
+ case 0:
+ g_string_append_len (out, start, inptr - start);
+ last_was_encoded = FALSE;
+ break;
+ case 1:
+ if (last_was_encoded)
+ g_string_append_c (out, ' ');
+
+ rfc2047_encode_word (out, start, inptr - start, "ISO-8859-1", CAMEL_MIME_IS_ESAFE);
+ last_was_encoded = TRUE;
+ break;
+ case 2:
+ if (last_was_encoded)
+ g_string_append_c (out, ' ');
+
+ rfc2047_encode_word (out, start, inptr - start,
+ camel_charset_best (start, inptr - start), CAMEL_MIME_IS_ESAFE);
+ last_was_encoded = TRUE;
+ break;
+ }
+
+ last_was_space = TRUE;
+ start = inptr;
+ word = NULL;
+ encoding = 0;
+ } else if (c > 127 && c < 256) {
+ encoding = MAX (encoding, 1);
+ last_was_space = FALSE;
+ } else if (c >= 256) {
+ encoding = MAX (encoding, 2);
+ last_was_space = FALSE;
+ } else if (!camel_mime_is_lwsp (c)) {
+ last_was_space = FALSE;
+ }
+
+ if (!(c < 256 && camel_mime_is_lwsp (c)) && !word)
+ word = inptr;
+
+ inptr = newinptr;
+ }
+
+ if (inptr - start) {
+ if (word && !(last_was_encoded && encoding)) {
+ g_string_append_len (out, start, word - start);
+ start = word;
+ }
+
+ switch (encoding) {
+ case 0:
+ g_string_append_len (out, start, inptr - start);
+ break;
+ case 1:
+ if (last_was_encoded)
+ g_string_append_c (out, ' ');
+
+ rfc2047_encode_word (out, start, inptr - start, "ISO-8859-1", CAMEL_MIME_IS_ESAFE);
+ break;
+ case 2:
+ if (last_was_encoded)
+ g_string_append_c (out, ' ');
+
+ rfc2047_encode_word (out, start, inptr - start,
+ camel_charset_best (start, inptr - start - 1), CAMEL_MIME_IS_ESAFE);
+ break;
+ }
+ }
+
+ outstr = out->str;
+ g_string_free (out, FALSE);
+
+ return outstr;
+}
+
+/* apply quoted-string rules to a string */
+static void
+quote_word(GString *out, gboolean do_quotes, const char *start, size_t len)
+{
+ int i, c;
+
+ /* TODO: What about folding on long lines? */
+ if (do_quotes)
+ g_string_append_c(out, '"');
+ for (i=0;i<len;i++) {
+ c = *start++;
+ if (c == '\"' || c=='\\' || c=='\r')
+ g_string_append_c(out, '\\');
+ g_string_append_c(out, c);
+ }
+ if (do_quotes)
+ g_string_append_c(out, '"');
+}
+
+/* incrementing possibility for the word type */
+enum _phrase_word_t {
+ WORD_ATOM,
+ WORD_QSTRING,
+ WORD_2047
+};
+
+struct _phrase_word {
+ const unsigned char *start, *end;
+ enum _phrase_word_t type;
+ int encoding;
+};
+
+static gboolean
+word_types_compatable (enum _phrase_word_t type1, enum _phrase_word_t type2)
+{
+ switch (type1) {
+ case WORD_ATOM:
+ return type2 == WORD_QSTRING;
+ case WORD_QSTRING:
+ return type2 != WORD_2047;
+ case WORD_2047:
+ return type2 == WORD_2047;
+ default:
+ return FALSE;
+ }
+}
+
+/* split the input into words with info about each word
+ * merge common word types clean up */
+static GList *
+header_encode_phrase_get_words (const unsigned char *in)
+{
+ const unsigned char *inptr = in, *start, *last;
+ struct _phrase_word *word;
+ enum _phrase_word_t type;
+ int encoding, count = 0;
+ GList *words = NULL;
+
+ /* break the input into words */
+ type = WORD_ATOM;
+ last = inptr;
+ start = inptr;
+ encoding = 0;
+ while (inptr && *inptr) {
+ gunichar c;
+ const char *newinptr;
+
+ newinptr = g_utf8_next_char (inptr);
+ c = g_utf8_get_char (inptr);
+
+ if (!g_unichar_validate (c)) {
+ w(g_warning ("Invalid UTF-8 sequence encountered (pos %d, char '%c'): %s",
+ (inptr - in), inptr[0], in));
+ inptr++;
+ continue;
+ }
+
+ inptr = newinptr;
+ if (g_unichar_isspace (c)) {
+ if (count > 0) {
+ word = g_new0 (struct _phrase_word, 1);
+ word->start = start;
+ word->end = last;
+ word->type = type;
+ word->encoding = encoding;
+ words = g_list_append (words, word);
+ count = 0;
+ }
+
+ start = inptr;
+ type = WORD_ATOM;
+ encoding = 0;
+ } else {
+ count++;
+ if (c < 128) {
+ if (!camel_mime_is_atom (c))
+ type = MAX (type, WORD_QSTRING);
+ } else if (c > 127 && c < 256) {
+ type = WORD_2047;
+ encoding = MAX (encoding, 1);
+ } else if (c >= 256) {
+ type = WORD_2047;
+ encoding = MAX (encoding, 2);
+ }
+ }
+
+ last = inptr;
+ }
+
+ if (count > 0) {
+ word = g_new0 (struct _phrase_word, 1);
+ word->start = start;
+ word->end = last;
+ word->type = type;
+ word->encoding = encoding;
+ words = g_list_append (words, word);
+ }
+
+ return words;
+}
+
+#define MERGED_WORD_LT_FOLDLEN(wordlen, type) ((type) == WORD_2047 ? (wordlen) < CAMEL_FOLD_PREENCODED : (wordlen) < (CAMEL_FOLD_SIZE - 8))
+
+static gboolean
+header_encode_phrase_merge_words (GList **wordsp)
+{
+ GList *wordl, *nextl, *words = *wordsp;
+ struct _phrase_word *word, *next;
+ gboolean merged = FALSE;
+
+ /* scan the list, checking for words of similar types that can be merged */
+ wordl = words;
+ while (wordl) {
+ word = wordl->data;
+ nextl = g_list_next (wordl);
+
+ while (nextl) {
+ next = nextl->data;
+ /* merge nodes of the same type AND we are not creating too long a string */
+ if (word_types_compatable (word->type, next->type)) {
+ if (MERGED_WORD_LT_FOLDLEN (next->end - word->start, MAX (word->type, next->type))) {
+ /* the resulting word type is the MAX of the 2 types */
+ word->type = MAX(word->type, next->type);
+
+ word->end = next->end;
+ words = g_list_remove_link (words, nextl);
+ g_list_free_1 (nextl);
+ g_free (next);
+
+ nextl = g_list_next (wordl);
+
+ merged = TRUE;
+ } else {
+ /* if it is going to be too long, make sure we include the
+ separating whitespace */
+ word->end = next->start;
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ wordl = g_list_next (wordl);
+ }
+
+ *wordsp = words;
+
+ return merged;
+}
+
+/* encodes a phrase sequence (different quoting/encoding rules to strings) */
+char *
+camel_header_encode_phrase (const unsigned char *in)
+{
+ struct _phrase_word *word = NULL, *last_word = NULL;
+ GList *words, *wordl;
+ GString *out;
+ char *outstr;
+
+ if (in == NULL)
+ return NULL;
+
+ words = header_encode_phrase_get_words (in);
+ if (!words)
+ return NULL;
+
+ while (header_encode_phrase_merge_words (&words))
+ ;
+
+ out = g_string_new ("");
+
+ /* output words now with spaces between them */
+ wordl = words;
+ while (wordl) {
+ const char *start;
+ size_t len;
+
+ word = wordl->data;
+
+ /* append correct number of spaces between words */
+ if (last_word && !(last_word->type == WORD_2047 && word->type == WORD_2047)) {
+ /* one or both of the words are not encoded so we write the spaces out untouched */
+ len = word->start - last_word->end;
+ out = g_string_append_len (out, last_word->end, len);
+ }
+
+ switch (word->type) {
+ case WORD_ATOM:
+ out = g_string_append_len (out, word->start, word->end - word->start);
+ break;
+ case WORD_QSTRING:
+ quote_word (out, TRUE, word->start, word->end - word->start);
+ break;
+ case WORD_2047:
+ if (last_word && last_word->type == WORD_2047) {
+ /* include the whitespace chars between these 2 words in the
+ resulting rfc2047 encoded word. */
+ len = word->end - last_word->end;
+ start = last_word->end;
+
+ /* encoded words need to be separated by linear whitespace */
+ g_string_append_c (out, ' ');
+ } else {
+ len = word->end - word->start;
+ start = word->start;
+ }
+
+ if (word->encoding == 1)
+ rfc2047_encode_word (out, start, len, "ISO-8859-1", CAMEL_MIME_IS_PSAFE);
+ else
+ rfc2047_encode_word (out, start, len,
+ camel_charset_best (start, len), CAMEL_MIME_IS_PSAFE);
+ break;
+ }
+
+ g_free (last_word);
+ wordl = g_list_next (wordl);
+
+ last_word = word;
+ }
+
+ /* and we no longer need the list */
+ g_free (word);
+ g_list_free (words);
+
+ outstr = out->str;
+ g_string_free (out, FALSE);
+
+ return outstr;
+}
+
+
+/* these are all internal parser functions */
+
+static char *
+decode_token (const char **in)
+{
+ const char *inptr = *in;
+ const char *start;
+
+ header_decode_lwsp (&inptr);
+ start = inptr;
+ while (camel_mime_is_ttoken (*inptr))
+ inptr++;
+ if (inptr > start) {
+ *in = inptr;
+ return g_strndup (start, inptr - start);
+ } else {
+ return NULL;
+ }
+}
+
+char *
+camel_header_token_decode(const char *in)
+{
+ if (in == NULL)
+ return NULL;
+
+ return decode_token(&in);
+}
+
+/*
+ <"> * ( <any char except <"> \, cr / \ <any char> ) <">
+*/
+static char *
+header_decode_quoted_string(const char **in)
+{
+ const char *inptr = *in;
+ char *out = NULL, *outptr;
+ size_t outlen;
+ int c;
+
+ header_decode_lwsp(&inptr);
+ if (*inptr == '"') {
+ const char *intmp;
+ int skip = 0;
+
+ /* first, calc length */
+ inptr++;
+ intmp = inptr;
+ while ( (c = *intmp++) && c!= '"') {
+ if (c=='\\' && *intmp) {
+ intmp++;
+ skip++;
+ }
+ }
+ outlen = intmp-inptr-skip;
+ out = outptr = g_malloc(outlen+1);
+ while ( (c = *inptr++) && c!= '"') {
+ if (c=='\\' && *inptr) {
+ c = *inptr++;
+ }
+ *outptr++ = c;
+ }
+ *outptr = '\0';
+ }
+ *in = inptr;
+ return out;
+}
+
+static char *
+header_decode_atom(const char **in)
+{
+ const char *inptr = *in, *start;
+
+ header_decode_lwsp(&inptr);
+ start = inptr;
+ while (camel_mime_is_atom(*inptr))
+ inptr++;
+ *in = inptr;
+ if (inptr > start)
+ return g_strndup(start, inptr-start);
+ else
+ return NULL;
+}
+
+static char *
+header_decode_word (const char **in)
+{
+ const char *inptr = *in;
+
+ header_decode_lwsp (&inptr);
+ if (*inptr == '"') {
+ *in = inptr;
+ return header_decode_quoted_string (in);
+ } else {
+ *in = inptr;
+ return header_decode_atom (in);
+ }
+}
+
+static char *
+header_decode_value(const char **in)
+{
+ const char *inptr = *in;
+
+ header_decode_lwsp(&inptr);
+ if (*inptr == '"') {
+ d(printf("decoding quoted string\n"));
+ return header_decode_quoted_string(in);
+ } else if (camel_mime_is_ttoken(*inptr)) {
+ d(printf("decoding token\n"));
+ /* this may not have the right specials for all params? */
+ return decode_token(in);
+ }
+ return NULL;
+}
+
+/* should this return -1 for no int? */
+int
+camel_header_decode_int(const char **in)
+{
+ const char *inptr = *in;
+ int c, v=0;
+
+ header_decode_lwsp(&inptr);
+ while ( (c=*inptr++ & 0xff)
+ && isdigit(c) ) {
+ v = v*10+(c-'0');
+ }
+ *in = inptr-1;
+ return v;
+}
+
+#define HEXVAL(c) (isdigit (c) ? (c) - '0' : tolower (c) - 'a' + 10)
+
+static char *
+hex_decode (const char *in, size_t len)
+{
+ const unsigned char *inend = in + len;
+ unsigned char *inptr, *outptr;
+ char *outbuf;
+
+ outptr = outbuf = g_malloc (len + 1);
+
+ inptr = (unsigned char *) in;
+ while (inptr < inend) {
+ if (*inptr == '%') {
+ if (isxdigit (inptr[1]) && isxdigit (inptr[2])) {
+ *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
+ inptr += 3;
+ } else
+ *outptr++ = *inptr++;
+ } else
+ *outptr++ = *inptr++;
+ }
+
+ *outptr = '\0';
+
+ return outbuf;
+}
+
+/* Tries to convert @in @from charset @to charset. Any failure, we get no data out rather than partial conversion */
+static char *
+header_convert(const char *to, const char *from, const char *in, size_t inlen)
+{
+ iconv_t ic;
+ size_t outlen, ret;
+ char *outbuf, *outbase, *result = NULL;
+
+ ic = e_iconv_open(to, from);
+ if (ic == (iconv_t) -1)
+ return NULL;
+
+ outlen = inlen * 6 + 16;
+ outbuf = outbase = g_malloc(outlen);
+
+ ret = e_iconv(ic, &in, &inlen, &outbuf, &outlen);
+ if (ret != (size_t) -1) {
+ e_iconv(ic, NULL, 0, &outbuf, &outlen);
+ *outbuf = '\0';
+ result = g_strdup(outbase);
+ }
+ e_iconv_close(ic);
+ g_free(outbase);
+
+ return result;
+}
+
+/* an rfc2184 encoded string looks something like:
+ * us-ascii'en'This%20is%20even%20more%20
+ */
+
+static char *
+rfc2184_decode (const char *in, size_t len)
+{
+ const char *inptr = in;
+ const char *inend = in + len;
+ const char *charset;
+ char *decoded, *decword, *encoding;
+
+ inptr = memchr (inptr, '\'', len);
+ if (!inptr)
+ return NULL;
+
+ encoding = g_alloca(inptr-in+1);
+ memcpy(encoding, in, inptr-in);
+ encoding[inptr-in] = 0;
+ charset = e_iconv_charset_name (encoding);
+
+ inptr = memchr (inptr + 1, '\'', inend - inptr - 1);
+ if (!inptr)
+ return NULL;
+ inptr++;
+ if (inptr >= inend)
+ return NULL;
+
+ decword = hex_decode (inptr, inend - inptr);
+ decoded = header_convert("UTF-8", charset, decword, strlen(decword));
+ g_free(decword);
+
+ return decoded;
+}
+
+char *
+camel_header_param (struct _camel_header_param *p, const char *name)
+{
+ while (p && strcasecmp (p->name, name) != 0)
+ p = p->next;
+ if (p)
+ return p->value;
+ return NULL;
+}
+
+struct _camel_header_param *
+camel_header_set_param (struct _camel_header_param **l, const char *name, const char *value)
+{
+ struct _camel_header_param *p = (struct _camel_header_param *)l, *pn;
+
+ if (name == NULL)
+ return NULL;
+
+ while (p->next) {
+ pn = p->next;
+ if (!strcasecmp (pn->name, name)) {
+ g_free (pn->value);
+ if (value) {
+ pn->value = g_strdup (value);
+ return pn;
+ } else {
+ p->next = pn->next;
+ g_free (pn->name);
+ g_free (pn);
+ return NULL;
+ }
+ }
+ p = pn;
+ }
+
+ if (value == NULL)
+ return NULL;
+
+ pn = g_malloc (sizeof (*pn));
+ pn->next = 0;
+ pn->name = g_strdup (name);
+ pn->value = g_strdup (value);
+ p->next = pn;
+
+ return pn;
+}
+
+const char *
+camel_content_type_param (CamelContentType *t, const char *name)
+{
+ if (t==NULL)
+ return NULL;
+ return camel_header_param (t->params, name);
+}
+
+void
+camel_content_type_set_param (CamelContentType *t, const char *name, const char *value)
+{
+ camel_header_set_param (&t->params, name, value);
+}
+
+/**
+ * camel_content_type_is:
+ * @ct: A content type specifier, or #NULL.
+ * @type: A type to check against.
+ * @subtype: A subtype to check against, or "*" to match any subtype.
+ *
+ * Returns #TRUE if the content type @ct is of type @type/@subtype.
+ * The subtype of "*" will match any subtype. If @ct is #NULL, then
+ * it will match the type "text/plain".
+ *
+ * Return value: #TRUE or #FALSE depending on the matching of the type.
+ **/
+int
+camel_content_type_is(CamelContentType *ct, const char *type, const char *subtype)
+{
+ /* no type == text/plain or text/"*" */
+ if (ct==NULL || (ct->type == NULL && ct->subtype == NULL)) {
+ return (!strcasecmp(type, "text")
+ && (!strcasecmp(subtype, "plain")
+ || !strcasecmp(subtype, "*")));
+ }
+
+ return (ct->type != NULL
+ && (!strcasecmp(ct->type, type)
+ && ((ct->subtype != NULL
+ && !strcasecmp(ct->subtype, subtype))
+ || !strcasecmp("*", subtype))));
+}
+
+void
+camel_header_param_list_free(struct _camel_header_param *p)
+{
+ struct _camel_header_param *n;
+
+ while (p) {
+ n = p->next;
+ g_free(p->name);
+ g_free(p->value);
+ g_free(p);
+ p = n;
+ }
+}
+
+CamelContentType *
+camel_content_type_new(const char *type, const char *subtype)
+{
+ CamelContentType *t = g_malloc(sizeof(*t));
+
+ t->type = g_strdup(type);
+ t->subtype = g_strdup(subtype);
+ t->params = NULL;
+ t->refcount = 1;
+ return t;
+}
+
+void
+camel_content_type_ref(CamelContentType *ct)
+{
+ if (ct)
+ ct->refcount++;
+}
+
+
+void
+camel_content_type_unref(CamelContentType *ct)
+{
+ if (ct) {
+ if (ct->refcount <= 1) {
+ camel_header_param_list_free(ct->params);
+ g_free(ct->type);
+ g_free(ct->subtype);
+ g_free(ct);
+ } else {
+ ct->refcount--;
+ }
+ }
+}
+
+/* for decoding email addresses, canonically */
+static char *
+header_decode_domain(const char **in)
+{
+ const char *inptr = *in, *start;
+ int go = TRUE;
+ char *ret;
+ GString *domain = g_string_new("");
+
+ /* domain ref | domain literal */
+ header_decode_lwsp(&inptr);
+ while (go) {
+ if (*inptr == '[') { /* domain literal */
+ domain = g_string_append_c(domain, '[');
+ inptr++;
+ header_decode_lwsp(&inptr);
+ start = inptr;
+ while (camel_mime_is_dtext(*inptr)) {
+ domain = g_string_append_c(domain, *inptr);
+ inptr++;
+ }
+ if (*inptr == ']') {
+ domain = g_string_append_c(domain, ']');
+ inptr++;
+ } else {
+ w(g_warning("closing ']' not found in domain: %s", *in));
+ }
+ } else {
+ char *a = header_decode_atom(&inptr);
+ if (a) {
+ domain = g_string_append(domain, a);
+ g_free(a);
+ } else {
+ w(g_warning("missing atom from domain-ref"));
+ break;
+ }
+ }
+ header_decode_lwsp(&inptr);
+ if (*inptr == '.') { /* next sub-domain? */
+ domain = g_string_append_c(domain, '.');
+ inptr++;
+ header_decode_lwsp(&inptr);
+ } else
+ go = FALSE;
+ }
+
+ *in = inptr;
+
+ ret = domain->str;
+ g_string_free(domain, FALSE);
+ return ret;
+}
+
+static char *
+header_decode_addrspec(const char **in)
+{
+ const char *inptr = *in;
+ char *word;
+ GString *addr = g_string_new("");
+
+ header_decode_lwsp(&inptr);
+
+ /* addr-spec */
+ word = header_decode_word (&inptr);
+ if (word) {
+ addr = g_string_append(addr, word);
+ header_decode_lwsp(&inptr);
+ g_free(word);
+ while (*inptr == '.' && word) {
+ inptr++;
+ addr = g_string_append_c(addr, '.');
+ word = header_decode_word (&inptr);
+ if (word) {
+ addr = g_string_append(addr, word);
+ header_decode_lwsp(&inptr);
+ g_free(word);
+ } else {
+ w(g_warning("Invalid address spec: %s", *in));
+ }
+ }
+ if (*inptr == '@') {
+ inptr++;
+ addr = g_string_append_c(addr, '@');
+ word = header_decode_domain(&inptr);
+ if (word) {
+ addr = g_string_append(addr, word);
+ g_free(word);
+ } else {
+ w(g_warning("Invalid address, missing domain: %s", *in));
+ }
+ } else {
+ w(g_warning("Invalid addr-spec, missing @: %s", *in));
+ }
+ } else {
+ w(g_warning("invalid addr-spec, no local part"));
+ }
+
+ /* FIXME: return null on error? */
+
+ *in = inptr;
+ word = addr->str;
+ g_string_free(addr, FALSE);
+ return word;
+}
+
+/*
+ address:
+ word *('.' word) @ domain |
+ *(word) '<' [ *('@' domain ) ':' ] word *( '.' word) @ domain |
+
+ 1*word ':' [ word ... etc (mailbox, as above) ] ';'
+ */
+
+/* mailbox:
+ word *( '.' word ) '@' domain
+ *(word) '<' [ *('@' domain ) ':' ] word *( '.' word) @ domain
+ */
+
+static struct _camel_header_address *
+header_decode_mailbox(const char **in, const char *charset)
+{
+ const char *inptr = *in;
+ char *pre;
+ int closeme = FALSE;
+ GString *addr;
+ GString *name = NULL;
+ struct _camel_header_address *address = NULL;
+ const char *comment = NULL;
+
+ addr = g_string_new("");
+
+ /* for each address */
+ pre = header_decode_word (&inptr);
+ header_decode_lwsp(&inptr);
+ if (!(*inptr == '.' || *inptr == '@' || *inptr==',' || *inptr=='\0')) {
+ /* ',' and '\0' required incase it is a simple address, no @ domain part (buggy writer) */
+ name = g_string_new ("");
+ while (pre) {
+ char *text, *last;
+
+ /* perform internationalised decoding, and append */
+ text = camel_header_decode_string (pre, charset);
+ g_string_append (name, text);
+ last = pre;
+ g_free(text);
+
+ pre = header_decode_word (&inptr);
+ if (pre) {
+ size_t l = strlen (last);
+ size_t p = strlen (pre);
+
+ /* dont append ' ' between sucsessive encoded words */
+ if ((l>6 && last[l-2] == '?' && last[l-1] == '=')
+ && (p>6 && pre[0] == '=' && pre[1] == '?')) {
+ /* dont append ' ' */
+ } else {
+ name = g_string_append_c(name, ' ');
+ }
+ } else {
+ /* Fix for stupidly-broken-mailers that like to put '.''s in names unquoted */
+ /* see bug #8147 */
+ while (!pre && *inptr && *inptr != '<') {
+ w(g_warning("Working around stupid mailer bug #5: unescaped characters in names"));
+ name = g_string_append_c(name, *inptr++);
+ pre = header_decode_word (&inptr);
+ }
+ }
+ g_free(last);
+ }
+ header_decode_lwsp(&inptr);
+ if (*inptr == '<') {
+ closeme = TRUE;
+ try_address_again:
+ inptr++;
+ header_decode_lwsp(&inptr);
+ if (*inptr == '@') {
+ while (*inptr == '@') {
+ inptr++;
+ header_decode_domain(&inptr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == ',') {
+ inptr++;
+ header_decode_lwsp(&inptr);
+ }
+ }
+ if (*inptr == ':') {
+ inptr++;
+ } else {
+ w(g_warning("broken route-address, missing ':': %s", *in));
+ }
+ }
+ pre = header_decode_word (&inptr);
+ header_decode_lwsp(&inptr);
+ } else {
+ w(g_warning("broken address? %s", *in));
+ }
+ }
+
+ if (pre) {
+ addr = g_string_append(addr, pre);
+ } else {
+ w(g_warning("No local-part for email address: %s", *in));
+ }
+
+ /* should be at word '.' localpart */
+ while (*inptr == '.' && pre) {
+ inptr++;
+ g_free(pre);
+ pre = header_decode_word (&inptr);
+ addr = g_string_append_c(addr, '.');
+ if (pre)
+ addr = g_string_append(addr, pre);
+ comment = inptr;
+ header_decode_lwsp(&inptr);
+ }
+ g_free(pre);
+
+ /* now at '@' domain part */
+ if (*inptr == '@') {
+ char *dom;
+
+ inptr++;
+ addr = g_string_append_c(addr, '@');
+ comment = inptr;
+ dom = header_decode_domain(&inptr);
+ addr = g_string_append(addr, dom);
+ g_free(dom);
+ } else if (*inptr != '>' || !closeme) {
+ /* If we get a <, the address was probably a name part, lets try again shall we? */
+ /* Another fix for seriously-broken-mailers */
+ if (*inptr && *inptr != ',') {
+ char *text;
+
+ w(g_warning("We didn't get an '@' where we expected in '%s', trying again", *in));
+ w(g_warning("Name is '%s', Addr is '%s' we're at '%s'\n", name?name->str:"<UNSET>", addr->str, inptr));
+
+ /* need to keep *inptr, as try_address_again will drop the current character */
+ if (*inptr == '<')
+ closeme = TRUE;
+ else
+ g_string_append_c(addr, *inptr);
+
+ /* check for address is encoded word ... */
+ text = camel_header_decode_string(addr->str, charset);
+ if (name == NULL) {
+ name = addr;
+ addr = g_string_new("");
+ if (text) {
+ g_string_truncate(name, 0);
+ g_string_append(name, text);
+ }
+ } else {
+ g_string_append(name, text?text:addr->str);
+ g_string_truncate(addr, 0);
+ }
+ g_free(text);
+
+ /* or maybe that we've added up a bunch of broken bits to make an encoded word */
+ text = rfc2047_decode_word(name->str, name->len);
+ if (text) {
+ g_string_truncate(name, 0);
+ g_string_append(name, text);
+ g_free(text);
+ }
+
+ goto try_address_again;
+ }
+ w(g_warning("invalid address, no '@' domain part at %c: %s", *inptr, *in));
+ }
+
+ if (closeme) {
+ header_decode_lwsp(&inptr);
+ if (*inptr == '>') {
+ inptr++;
+ } else {
+ w(g_warning("invalid route address, no closing '>': %s", *in));
+ }
+ } else if (name == NULL && comment != NULL && inptr>comment) { /* check for comment after address */
+ char *text, *tmp;
+ const char *comstart, *comend;
+
+ /* this is a bit messy, we go from the last known position, because
+ decode_domain/etc skip over any comments on the way */
+ /* FIXME: This wont detect comments inside the domain itself,
+ but nobody seems to use that feature anyway ... */
+
+ d(printf("checking for comment from '%s'\n", comment));
+
+ comstart = strchr(comment, '(');
+ if (comstart) {
+ comstart++;
+ header_decode_lwsp(&inptr);
+ comend = inptr-1;
+ while (comend > comstart && comend[0] != ')')
+ comend--;
+
+ if (comend > comstart) {
+ d(printf(" looking at subset '%.*s'\n", comend-comstart, comstart));
+ tmp = g_strndup (comstart, comend-comstart);
+ text = camel_header_decode_string (tmp, charset);
+ name = g_string_new (text);
+ g_free (tmp);
+ g_free (text);
+ }
+ }
+ }
+
+ *in = inptr;
+
+ if (addr->len > 0) {
+ if (!g_utf8_validate (addr->str, addr->len, NULL)) {
+ /* workaround for invalid addr-specs containing 8bit chars (see bug #42170 for details) */
+ const char *locale_charset;
+ GString *out;
+
+ locale_charset = e_iconv_locale_charset ();
+
+ out = g_string_new ("");
+
+ if ((charset == NULL || !append_8bit (out, addr->str, addr->len, charset))
+ && (locale_charset == NULL || !append_8bit (out, addr->str, addr->len, locale_charset)))
+ append_latin1 (out, addr->str, addr->len);
+
+ g_string_free (addr, TRUE);
+ addr = out;
+ }
+
+ address = camel_header_address_new_name(name ? name->str : "", addr->str);
+ }
+
+ d(printf("got mailbox: %s\n", addr->str));
+
+ g_string_free(addr, TRUE);
+ if (name)
+ g_string_free(name, TRUE);
+
+ return address;
+}
+
+static struct _camel_header_address *
+header_decode_address(const char **in, const char *charset)
+{
+ const char *inptr = *in;
+ char *pre;
+ GString *group = g_string_new("");
+ struct _camel_header_address *addr = NULL, *member;
+
+ /* pre-scan, trying to work out format, discard results */
+ header_decode_lwsp(&inptr);
+ while ((pre = header_decode_word (&inptr))) {
+ group = g_string_append(group, pre);
+ group = g_string_append(group, " ");
+ g_free(pre);
+ }
+ header_decode_lwsp(&inptr);
+ if (*inptr == ':') {
+ d(printf("group detected: %s\n", group->str));
+ addr = camel_header_address_new_group(group->str);
+ /* that was a group spec, scan mailbox's */
+ inptr++;
+ /* FIXME: check rfc 2047 encodings of words, here or above in the loop */
+ header_decode_lwsp(&inptr);
+ if (*inptr != ';') {
+ int go = TRUE;
+ do {
+ member = header_decode_mailbox(&inptr, charset);
+ if (member)
+ camel_header_address_add_member(addr, member);
+ header_decode_lwsp(&inptr);
+ if (*inptr == ',')
+ inptr++;
+ else
+ go = FALSE;
+ } while (go);
+ if (*inptr == ';') {
+ inptr++;
+ } else {
+ w(g_warning("Invalid group spec, missing closing ';': %s", *in));
+ }
+ } else {
+ inptr++;
+ }
+ *in = inptr;
+ } else {
+ addr = header_decode_mailbox(in, charset);
+ }
+
+ g_string_free(group, TRUE);
+
+ return addr;
+}
+
+static char *
+header_msgid_decode_internal(const char **in)
+{
+ const char *inptr = *in;
+ char *msgid = NULL;
+
+ d(printf("decoding Message-ID: '%s'\n", *in));
+
+ header_decode_lwsp(&inptr);
+ if (*inptr == '<') {
+ inptr++;
+ header_decode_lwsp(&inptr);
+ msgid = header_decode_addrspec(&inptr);
+ if (msgid) {
+ header_decode_lwsp(&inptr);
+ if (*inptr == '>') {
+ inptr++;
+ } else {
+ w(g_warning("Missing closing '>' on message id: %s", *in));
+ }
+ } else {
+ w(g_warning("Cannot find message id in: %s", *in));
+ }
+ } else {
+ w(g_warning("missing opening '<' on message id: %s", *in));
+ }
+ *in = inptr;
+
+ return msgid;
+}
+
+char *
+camel_header_msgid_decode(const char *in)
+{
+ if (in == NULL)
+ return NULL;
+
+ return header_msgid_decode_internal(&in);
+}
+
+char *
+camel_header_contentid_decode (const char *in)
+{
+ const char *inptr = in;
+ gboolean at = FALSE;
+ GString *addr;
+ char *buf;
+
+ d(printf("decoding Content-ID: '%s'\n", in));
+
+ header_decode_lwsp (&inptr);
+
+ /* some lame mailers quote the Content-Id */
+ if (*inptr == '"')
+ inptr++;
+
+ /* make sure the content-id is not "" which can happen if we get a
+ * content-id such as <.@> (which Eudora likes to use...) */
+ if ((buf = camel_header_msgid_decode (inptr)) != NULL && *buf)
+ return buf;
+
+ g_free (buf);
+
+ /* ugh, not a valid msg-id - try to get something useful out of it then? */
+ inptr = in;
+ header_decode_lwsp (&inptr);
+ if (*inptr == '<') {
+ inptr++;
+ header_decode_lwsp (&inptr);
+ }
+
+ /* Eudora has been known to use <.@> as a content-id */
+ if (!(buf = header_decode_word (&inptr)) && !strchr (".@", *inptr))
+ return NULL;
+
+ addr = g_string_new ("");
+ header_decode_lwsp (&inptr);
+ while (buf != NULL || *inptr == '.' || (*inptr == '@' && !at)) {
+ if (buf != NULL) {
+ g_string_append (addr, buf);
+ g_free (buf);
+ buf = NULL;
+ }
+
+ if (!at) {
+ if (*inptr == '.') {
+ g_string_append_c (addr, *inptr++);
+ buf = header_decode_word (&inptr);
+ } else if (*inptr == '@') {
+ g_string_append_c (addr, *inptr++);
+ buf = header_decode_word (&inptr);
+ at = TRUE;
+ }
+ } else if (strchr (".[]", *inptr)) {
+ g_string_append_c (addr, *inptr++);
+ buf = header_decode_atom (&inptr);
+ }
+
+ header_decode_lwsp (&inptr);
+ }
+
+ buf = addr->str;
+ g_string_free (addr, FALSE);
+
+ return buf;
+}
+
+void
+camel_header_references_list_append_asis(struct _camel_header_references **list, char *ref)
+{
+ struct _camel_header_references *w = (struct _camel_header_references *)list, *n;
+ while (w->next)
+ w = w->next;
+ n = g_malloc(sizeof(*n));
+ n->id = ref;
+ n->next = 0;
+ w->next = n;
+}
+
+int
+camel_header_references_list_size(struct _camel_header_references **list)
+{
+ int count = 0;
+ struct _camel_header_references *w = *list;
+ while (w) {
+ count++;
+ w = w->next;
+ }
+ return count;
+}
+
+void
+camel_header_references_list_clear(struct _camel_header_references **list)
+{
+ struct _camel_header_references *w = *list, *n;
+ while (w) {
+ n = w->next;
+ g_free(w->id);
+ g_free(w);
+ w = n;
+ }
+ *list = NULL;
+}
+
+static void
+header_references_decode_single (const char **in, struct _camel_header_references **head)
+{
+ struct _camel_header_references *ref;
+ const char *inptr = *in;
+ char *id, *word;
+
+ while (*inptr) {
+ header_decode_lwsp (&inptr);
+ if (*inptr == '<') {
+ id = header_msgid_decode_internal (&inptr);
+ if (id) {
+ ref = g_malloc (sizeof (struct _camel_header_references));
+ ref->next = *head;
+ ref->id = id;
+ *head = ref;
+ break;
+ }
+ } else {
+ word = header_decode_word (&inptr);
+ if (word)
+ g_free (word);
+ else if (*inptr != '\0')
+ inptr++; /* Stupid mailer tricks */
+ }
+ }
+
+ *in = inptr;
+}
+
+/* TODO: why is this needed? Can't the other interface also work? */
+struct _camel_header_references *
+camel_header_references_inreplyto_decode (const char *in)
+{
+ struct _camel_header_references *ref = NULL;
+
+ if (in == NULL || in[0] == '\0')
+ return NULL;
+
+ header_references_decode_single (&in, &ref);
+
+ return ref;
+}
+
+/* generate a list of references, from most recent up */
+struct _camel_header_references *
+camel_header_references_decode (const char *in)
+{
+ struct _camel_header_references *refs = NULL;
+
+ if (in == NULL || in[0] == '\0')
+ return NULL;
+
+ while (*in)
+ header_references_decode_single (&in, &refs);
+
+ return refs;
+}
+
+struct _camel_header_references *
+camel_header_references_dup(const struct _camel_header_references *list)
+{
+ struct _camel_header_references *new = NULL, *tmp;
+
+ while (list) {
+ tmp = g_new(struct _camel_header_references, 1);
+ tmp->next = new;
+ tmp->id = g_strdup(list->id);
+ new = tmp;
+ list = list->next;
+ }
+ return new;
+}
+
+struct _camel_header_address *
+camel_header_mailbox_decode(const char *in, const char *charset)
+{
+ if (in == NULL)
+ return NULL;
+
+ return header_decode_mailbox(&in, charset);
+}
+
+struct _camel_header_address *
+camel_header_address_decode(const char *in, const char *charset)
+{
+ const char *inptr = in, *last;
+ struct _camel_header_address *list = NULL, *addr;
+
+ d(printf("decoding To: '%s'\n", in));
+
+ if (in == NULL)
+ return NULL;
+
+ header_decode_lwsp(&inptr);
+ if (*inptr == 0)
+ return NULL;
+
+ do {
+ last = inptr;
+ addr = header_decode_address(&inptr, charset);
+ if (addr)
+ camel_header_address_list_append(&list, addr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == ',')
+ inptr++;
+ else
+ break;
+ } while (inptr != last);
+
+ if (*inptr) {
+ w(g_warning("Invalid input detected at %c (%d): %s\n or at: %s", *inptr, inptr-in, in, inptr));
+ }
+
+ if (inptr == last) {
+ w(g_warning("detected invalid input loop at : %s", last));
+ }
+
+ return list;
+}
+
+struct _camel_header_newsgroup *
+camel_header_newsgroups_decode(const char *in)
+{
+ const char *inptr = in;
+ register char c;
+ struct _camel_header_newsgroup *head, *last, *ng;
+ const char *start;
+
+ head = NULL;
+ last = (struct _camel_header_newsgroup *)&head;
+
+ do {
+ header_decode_lwsp(&inptr);
+ start = inptr;
+ while ((c = *inptr++) && !camel_mime_is_lwsp(c) && c != ',')
+ ;
+ if (start != inptr-1) {
+ ng = g_malloc(sizeof(*ng));
+ ng->newsgroup = g_strndup(start, inptr-start-1);
+ ng->next = NULL;
+ last->next = ng;
+ last = ng;
+ }
+ } while (c);
+
+ return head;
+}
+
+void
+camel_header_newsgroups_free(struct _camel_header_newsgroup *ng)
+{
+ while (ng) {
+ struct _camel_header_newsgroup *nng = ng->next;
+
+ g_free(ng->newsgroup);
+ g_free(ng);
+ ng = nng;
+ }
+}
+
+/* this must be kept in sync with the header */
+static const char *encodings[] = {
+ "",
+ "7bit",
+ "8bit",
+ "base64",
+ "quoted-printable",
+ "binary",
+ "x-uuencode",
+};
+
+const char *
+camel_transfer_encoding_to_string (CamelTransferEncoding encoding)
+{
+ if (encoding >= sizeof (encodings) / sizeof (encodings[0]))
+ encoding = 0;
+
+ return encodings[encoding];
+}
+
+CamelTransferEncoding
+camel_transfer_encoding_from_string (const char *string)
+{
+ int i;
+
+ if (string != NULL) {
+ for (i = 0; i < sizeof (encodings) / sizeof (encodings[0]); i++)
+ if (!strcasecmp (string, encodings[i]))
+ return i;
+ }
+
+ return CAMEL_TRANSFER_ENCODING_DEFAULT;
+}
+
+void
+camel_header_mime_decode(const char *in, int *maj, int *min)
+{
+ const char *inptr = in;
+ int major=-1, minor=-1;
+
+ d(printf("decoding MIME-Version: '%s'\n", in));
+
+ if (in != NULL) {
+ header_decode_lwsp(&inptr);
+ if (isdigit(*inptr)) {
+ major = camel_header_decode_int(&inptr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == '.') {
+ inptr++;
+ header_decode_lwsp(&inptr);
+ if (isdigit(*inptr))
+ minor = camel_header_decode_int(&inptr);
+ }
+ }
+ }
+
+ if (maj)
+ *maj = major;
+ if (min)
+ *min = minor;
+
+ d(printf("major = %d, minor = %d\n", major, minor));
+}
+
+struct _rfc2184_param {
+ struct _camel_header_param param;
+ int index;
+};
+
+static int
+rfc2184_param_cmp(const void *ap, const void *bp)
+{
+ const struct _rfc2184_param *a = *(void **)ap;
+ const struct _rfc2184_param *b = *(void **)bp;
+ int res;
+
+ res = strcmp(a->param.name, b->param.name);
+ if (res == 0) {
+ if (a->index > b->index)
+ res = 1;
+ else if (a->index < b->index)
+ res = -1;
+ }
+
+ return res;
+}
+
+/* NB: Steals name and value */
+static struct _camel_header_param *
+header_append_param(struct _camel_header_param *last, char *name, char *value)
+{
+ struct _camel_header_param *node;
+
+ /* This handles -
+ 8 bit data in parameters, illegal, tries to convert using locale, or just safens it up.
+ rfc2047 ecoded parameters, illegal, decodes them anyway. Some Outlook & Mozilla do this?
+ */
+ node = g_malloc(sizeof(*node));
+ last->next = node;
+ node->next = NULL;
+ node->name = name;
+ if (strncmp(value, "=?", 2) == 0
+ && (node->value = header_decode_text(value, strlen(value), FALSE, NULL))) {
+ g_free(value);
+ } else if (!g_utf8_validate(value, -1, NULL)) {
+ const char * charset = e_iconv_locale_charset();
+
+ if ((node->value = header_convert("UTF-8", charset?charset:"ISO-8859-1", value, strlen(value)))) {
+ g_free(value);
+ } else {
+ node->value = value;
+ for (;*value;value++)
+ if (!isascii((unsigned char)*value))
+ *value = '_';
+ }
+ } else
+ node->value = value;
+
+ return node;
+}
+
+static struct _camel_header_param *
+header_decode_param_list (const char **in)
+{
+ struct _camel_header_param *head = NULL, *last = (struct _camel_header_param *)&head;
+ GPtrArray *split = NULL;
+ const char *inptr = *in;
+ struct _rfc2184_param *work;
+ char *tmp;
+
+ /* Dump parameters into the output list, in the order found. RFC 2184 split parameters are kept in an array */
+ header_decode_lwsp(&inptr);
+ while (*inptr == ';') {
+ char *name;
+ char *value = NULL;
+
+ inptr++;
+ name = decode_token(&inptr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == '=') {
+ inptr++;
+ value = header_decode_value(&inptr);
+ }
+
+ if (name && value) {
+ char *index = strchr(name, '*');
+
+ if (index) {
+ if (index[1] == 0) {
+ /* VAL*="foo", decode immediately and append */
+ *index = 0;
+ tmp = rfc2184_decode(value, strlen(value));
+ if (tmp) {
+ g_free(value);
+ value = tmp;
+ }
+ last = header_append_param(last, name, value);
+ } else {
+ /* VAL*1="foo", save for later */
+ *index++ = 0;
+ work = g_malloc(sizeof(*work));
+ work->param.name = name;
+ work->param.value = value;
+ work->index = atoi(index);
+ if (split == NULL)
+ split = g_ptr_array_new();
+ g_ptr_array_add(split, work);
+ }
+ } else {
+ last = header_append_param(last, name, value);
+ }
+ } else {
+ g_free(name);
+ g_free(value);
+ }
+
+ header_decode_lwsp(&inptr);
+ }
+
+ /* Rejoin any RFC 2184 split parameters in the proper order */
+ /* Parameters with the same index will be concatenated in undefined order */
+ if (split) {
+ GString *value = g_string_new("");
+ struct _rfc2184_param *first;
+ int i;
+
+ qsort(split->pdata, split->len, sizeof(split->pdata[0]), rfc2184_param_cmp);
+ first = split->pdata[0];
+ for (i=0;i<split->len;i++) {
+ work = split->pdata[i];
+ if (split->len-1 == i)
+ g_string_append(value, work->param.value);
+ if (split->len-1 == i || strcmp(work->param.name, first->param.name) != 0) {
+ tmp = rfc2184_decode(value->str, value->len);
+ if (tmp == NULL)
+ tmp = g_strdup(value->str);
+
+ last = header_append_param(last, g_strdup(first->param.name), tmp);
+ g_string_truncate(value, 0);
+ first = work;
+ }
+ if (split->len-1 != i)
+ g_string_append(value, work->param.value);
+ }
+ g_string_free(value, TRUE);
+ for (i=0;i<split->len;i++) {
+ work = split->pdata[i];
+ g_free(work->param.name);
+ g_free(work->param.value);
+ g_free(work);
+ }
+ g_ptr_array_free(split, TRUE);
+ }
+
+ *in = inptr;
+
+ return head;
+}
+
+struct _camel_header_param *
+camel_header_param_list_decode(const char *in)
+{
+ if (in == NULL)
+ return NULL;
+
+ return header_decode_param_list(&in);
+}
+
+static char *
+header_encode_param (const unsigned char *in, gboolean *encoded)
+{
+ const unsigned char *inptr = in;
+ unsigned char *outbuf = NULL;
+ const char *charset;
+ int encoding;
+ GString *out;
+ guint32 c;
+
+ *encoded = FALSE;
+
+ g_return_val_if_fail (in != NULL, NULL);
+
+ /* do a quick us-ascii check (the common case?) */
+ while (*inptr) {
+ if (*inptr > 127)
+ break;
+ inptr++;
+ }
+
+ if (*inptr == '\0')
+ return g_strdup (in);
+
+ inptr = in;
+ encoding = 0;
+ while ( encoding !=2 && (c = camel_utf8_getc(&inptr)) ) {
+ if (c > 127 && c < 256)
+ encoding = MAX (encoding, 1);
+ else if (c >= 256)
+ encoding = MAX (encoding, 2);
+ }
+
+ if (encoding == 2)
+ charset = camel_charset_best(in, strlen(in));
+ else
+ charset = "iso-8859-1";
+
+ if (g_ascii_strcasecmp(charset, "UTF-8") != 0
+ && (outbuf = header_convert(charset, "UTF-8", in, strlen(in)))) {
+ inptr = outbuf;
+ } else {
+ charset = "UTF-8";
+ inptr = in;
+ }
+
+ /* FIXME: set the 'language' as well, assuming we can get that info...? */
+ out = g_string_new (charset);
+ g_string_append(out, "''");
+
+ while ( (c = *inptr++) ) {
+ if (camel_mime_is_attrchar(c))
+ g_string_append_c (out, c);
+ else
+ g_string_append_printf (out, "%%%c%c", tohex[(c >> 4) & 0xf], tohex[c & 0xf]);
+ }
+ g_free (outbuf);
+
+ outbuf = out->str;
+ g_string_free (out, FALSE);
+ *encoded = TRUE;
+
+ return outbuf;
+}
+
+void
+camel_header_param_list_format_append (GString *out, struct _camel_header_param *p)
+{
+ int used = out->len;
+
+ while (p) {
+ gboolean encoded = FALSE;
+ gboolean quote = FALSE;
+ int here = out->len;
+ size_t nlen, vlen;
+ char *value;
+
+ if (!p->value) {
+ p = p->next;
+ continue;
+ }
+
+ value = header_encode_param (p->value, &encoded);
+ if (!value) {
+ w(g_warning ("appending parameter %s=%s violates rfc2184", p->name, p->value));
+ value = g_strdup (p->value);
+ }
+
+ if (!encoded) {
+ char *ch;
+
+ for (ch = value; *ch; ch++) {
+ if (camel_mime_is_tspecial (*ch) || camel_mime_is_lwsp (*ch))
+ break;
+ }
+
+ quote = ch && *ch;
+ }
+
+ nlen = strlen (p->name);
+ vlen = strlen (value);
+
+ if (used + nlen + vlen > CAMEL_FOLD_SIZE - 8) {
+ out = g_string_append (out, ";\n\t");
+ here = out->len;
+ used = 0;
+ } else
+ out = g_string_append (out, "; ");
+
+ if (nlen + vlen > CAMEL_FOLD_SIZE - 8) {
+ /* we need to do special rfc2184 parameter wrapping */
+ int maxlen = CAMEL_FOLD_SIZE - (nlen + 8);
+ char *inptr, *inend;
+ int i = 0;
+
+ inptr = value;
+ inend = value + vlen;
+
+ while (inptr < inend) {
+ char *ptr = inptr + MIN (inend - inptr, maxlen);
+
+ if (encoded && ptr < inend) {
+ /* be careful not to break an encoded char (ie %20) */
+ char *q = ptr;
+ int j = 2;
+
+ for ( ; j > 0 && q > inptr && *q != '%'; j--, q--);
+ if (*q == '%')
+ ptr = q;
+ }
+
+ if (i != 0) {
+ g_string_append (out, ";\n\t");
+ here = out->len;
+ used = 0;
+ }
+
+ g_string_append_printf (out, "%s*%d%s=", p->name, i++, encoded ? "*" : "");
+ if (encoded || !quote)
+ g_string_append_len (out, inptr, ptr - inptr);
+ else
+ quote_word (out, TRUE, inptr, ptr - inptr);
+
+ d(printf ("wrote: %s\n", out->str + here));
+
+ used += (out->len - here);
+
+ inptr = ptr;
+ }
+ } else {
+ g_string_append_printf (out, "%s%s=", p->name, encoded ? "*" : "");
+
+ if (encoded || !quote)
+ g_string_append (out, value);
+ else
+ quote_word (out, TRUE, value, vlen);
+
+ used += (out->len - here);
+ }
+
+ g_free (value);
+
+ p = p->next;
+ }
+}
+
+char *
+camel_header_param_list_format(struct _camel_header_param *p)
+{
+ GString *out = g_string_new("");
+ char *ret;
+
+ camel_header_param_list_format_append(out, p);
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+CamelContentType *
+camel_content_type_decode(const char *in)
+{
+ const char *inptr = in;
+ char *type, *subtype = NULL;
+ CamelContentType *t = NULL;
+
+ if (in==NULL)
+ return NULL;
+
+ type = decode_token(&inptr);
+ header_decode_lwsp(&inptr);
+ if (type) {
+ if (*inptr == '/') {
+ inptr++;
+ subtype = decode_token(&inptr);
+ }
+ if (subtype == NULL && (!strcasecmp(type, "text"))) {
+ w(g_warning("text type with no subtype, resorting to text/plain: %s", in));
+ subtype = g_strdup("plain");
+ }
+ if (subtype == NULL) {
+ w(g_warning("MIME type with no subtype: %s", in));
+ }
+
+ t = camel_content_type_new(type, subtype);
+ t->params = header_decode_param_list(&inptr);
+ g_free(type);
+ g_free(subtype);
+ } else {
+ g_free(type);
+ d(printf("cannot find MIME type in header (2) '%s'", in));
+ }
+ return t;
+}
+
+void
+camel_content_type_dump(CamelContentType *ct)
+{
+ struct _camel_header_param *p;
+
+ printf("Content-Type: ");
+ if (ct==NULL) {
+ printf("<NULL>\n");
+ return;
+ }
+ printf("%s / %s", ct->type, ct->subtype);
+ p = ct->params;
+ if (p) {
+ while (p) {
+ printf(";\n\t%s=\"%s\"", p->name, p->value);
+ p = p->next;
+ }
+ }
+ printf("\n");
+}
+
+char *
+camel_content_type_format (CamelContentType *ct)
+{
+ GString *out;
+ char *ret;
+
+ if (ct == NULL)
+ return NULL;
+
+ out = g_string_new ("");
+ if (ct->type == NULL) {
+ g_string_append_printf (out, "text/plain");
+ w(g_warning ("Content-Type with no main type"));
+ } else if (ct->subtype == NULL) {
+ w(g_warning ("Content-Type with no sub type: %s", ct->type));
+ if (!strcasecmp (ct->type, "multipart"))
+ g_string_append_printf (out, "%s/mixed", ct->type);
+ else
+ g_string_append_printf (out, "%s", ct->type);
+ } else {
+ g_string_append_printf (out, "%s/%s", ct->type, ct->subtype);
+ }
+ camel_header_param_list_format_append (out, ct->params);
+
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ return ret;
+}
+
+char *
+camel_content_type_simple (CamelContentType *ct)
+{
+ if (ct->type == NULL) {
+ w(g_warning ("Content-Type with no main type"));
+ return g_strdup ("text/plain");
+ } else if (ct->subtype == NULL) {
+ w(g_warning ("Content-Type with no sub type: %s", ct->type));
+ if (!strcasecmp (ct->type, "multipart"))
+ return g_strdup_printf ("%s/mixed", ct->type);
+ else
+ return g_strdup (ct->type);
+ } else
+ return g_strdup_printf ("%s/%s", ct->type, ct->subtype);
+}
+
+char *
+camel_content_transfer_encoding_decode (const char *in)
+{
+ if (in)
+ return decode_token (&in);
+
+ return NULL;
+}
+
+CamelContentDisposition *
+camel_content_disposition_decode(const char *in)
+{
+ CamelContentDisposition *d = NULL;
+ const char *inptr = in;
+
+ if (in == NULL)
+ return NULL;
+
+ d = g_malloc(sizeof(*d));
+ d->refcount = 1;
+ d->disposition = decode_token(&inptr);
+ if (d->disposition == NULL)
+ w(g_warning("Empty disposition type"));
+ d->params = header_decode_param_list(&inptr);
+ return d;
+}
+
+void
+camel_content_disposition_ref(CamelContentDisposition *d)
+{
+ if (d)
+ d->refcount++;
+}
+
+void
+camel_content_disposition_unref(CamelContentDisposition *d)
+{
+ if (d) {
+ if (d->refcount<=1) {
+ camel_header_param_list_free(d->params);
+ g_free(d->disposition);
+ g_free(d);
+ } else {
+ d->refcount--;
+ }
+ }
+}
+
+char *
+camel_content_disposition_format(CamelContentDisposition *d)
+{
+ GString *out;
+ char *ret;
+
+ if (d==NULL)
+ return NULL;
+
+ out = g_string_new("");
+ if (d->disposition)
+ out = g_string_append(out, d->disposition);
+ else
+ out = g_string_append(out, "attachment");
+ camel_header_param_list_format_append(out, d->params);
+
+ ret = out->str;
+ g_string_free(out, FALSE);
+ return ret;
+}
+
+/* hrm, is there a library for this shit? */
+static struct {
+ char *name;
+ int offset;
+} tz_offsets [] = {
+ { "UT", 0 },
+ { "GMT", 0 },
+ { "EST", -500 }, /* these are all US timezones. bloody yanks */
+ { "EDT", -400 },
+ { "CST", -600 },
+ { "CDT", -500 },
+ { "MST", -700 },
+ { "MDT", -600 },
+ { "PST", -800 },
+ { "PDT", -700 },
+ { "Z", 0 },
+ { "A", -100 },
+ { "M", -1200 },
+ { "N", 100 },
+ { "Y", 1200 },
+};
+
+static char *tz_months [] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static char *tz_days [] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+char *
+camel_header_format_date(time_t time, int offset)
+{
+ struct tm tm;
+
+ d(printf("offset = %d\n", offset));
+
+ d(printf("converting date %s", ctime(&time)));
+
+ time += ((offset / 100) * (60*60)) + (offset % 100)*60;
+
+ d(printf("converting date %s", ctime(&time)));
+
+ gmtime_r (&time, &tm);
+
+ return g_strdup_printf("%s, %02d %s %04d %02d:%02d:%02d %+05d",
+ tz_days[tm.tm_wday],
+ tm.tm_mday, tz_months[tm.tm_mon],
+ tm.tm_year + 1900,
+ tm.tm_hour, tm.tm_min, tm.tm_sec,
+ offset);
+}
+
+/* convert a date to time_t representation */
+/* this is an awful mess oh well */
+time_t
+camel_header_decode_date(const char *in, int *saveoffset)
+{
+ const char *inptr = in;
+ char *monthname;
+ gboolean foundmonth;
+ int year, offset = 0;
+ struct tm tm;
+ int i;
+ time_t t;
+
+ if (in == NULL) {
+ if (saveoffset)
+ *saveoffset = 0;
+ return 0;
+ }
+
+ d(printf ("\ndecoding date '%s'\n", inptr));
+
+ memset (&tm, 0, sizeof(tm));
+
+ header_decode_lwsp (&inptr);
+ if (!isdigit (*inptr)) {
+ char *day = decode_token (&inptr);
+ /* we dont really care about the day, it's only for display */
+ if (day) {
+ d(printf ("got day: %s\n", day));
+ g_free (day);
+ header_decode_lwsp (&inptr);
+ if (*inptr == ',') {
+ inptr++;
+ } else {
+#ifndef CLEAN_DATE
+ return parse_broken_date (in, saveoffset);
+#else
+ if (saveoffset)
+ *saveoffset = 0;
+ return 0;
+#endif /* ! CLEAN_DATE */
+ }
+ }
+ }
+ tm.tm_mday = camel_header_decode_int(&inptr);
+#ifndef CLEAN_DATE
+ if (tm.tm_mday == 0) {
+ return parse_broken_date (in, saveoffset);
+ }
+#endif /* ! CLEAN_DATE */
+
+ monthname = decode_token(&inptr);
+ foundmonth = FALSE;
+ if (monthname) {
+ for (i=0;i<sizeof(tz_months)/sizeof(tz_months[0]);i++) {
+ if (!strcasecmp(tz_months[i], monthname)) {
+ tm.tm_mon = i;
+ foundmonth = TRUE;
+ break;
+ }
+ }
+ g_free(monthname);
+ }
+#ifndef CLEAN_DATE
+ if (!foundmonth) {
+ return parse_broken_date (in, saveoffset);
+ }
+#endif /* ! CLEAN_DATE */
+
+ year = camel_header_decode_int(&inptr);
+ if (year < 69) {
+ tm.tm_year = 100 + year;
+ } else if (year < 100) {
+ tm.tm_year = year;
+ } else if (year >= 100 && year < 1900) {
+ tm.tm_year = year;
+ } else {
+ tm.tm_year = year - 1900;
+ }
+ /* get the time ... yurck */
+ tm.tm_hour = camel_header_decode_int(&inptr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == ':')
+ inptr++;
+ tm.tm_min = camel_header_decode_int(&inptr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == ':')
+ inptr++;
+ tm.tm_sec = camel_header_decode_int(&inptr);
+ header_decode_lwsp(&inptr);
+ if (*inptr == '+'
+ || *inptr == '-') {
+ offset = (*inptr++)=='-'?-1:1;
+ offset = offset * camel_header_decode_int(&inptr);
+ d(printf("abs signed offset = %d\n", offset));
+ if (offset < -1200 || offset > 1400)
+ offset = 0;
+ } else if (isdigit(*inptr)) {
+ offset = camel_header_decode_int(&inptr);
+ d(printf("abs offset = %d\n", offset));
+ if (offset < -1200 || offset > 1400)
+ offset = 0;
+ } else {
+ char *tz = decode_token(&inptr);
+
+ if (tz) {
+ for (i=0;i<sizeof(tz_offsets)/sizeof(tz_offsets[0]);i++) {
+ if (!strcasecmp(tz_offsets[i].name, tz)) {
+ offset = tz_offsets[i].offset;
+ break;
+ }
+ }
+ g_free(tz);
+ }
+ /* some broken mailers seem to put in things like GMT+1030 instead of just +1030 */
+ header_decode_lwsp(&inptr);
+ if (*inptr == '+' || *inptr == '-') {
+ int sign = (*inptr++)=='-'?-1:1;
+ offset = offset + (camel_header_decode_int(&inptr)*sign);
+ }
+ d(printf("named offset = %d\n", offset));
+ }
+
+ t = e_mktime_utc(&tm);
+
+ /* t is now GMT of the time we want, but not offset by the timezone ... */
+
+ d(printf(" gmt normalized? = %s\n", ctime(&t)));
+
+ /* this should convert the time to the GMT equiv time */
+ t -= ( (offset/100) * 60*60) + (offset % 100)*60;
+
+ d(printf(" gmt normalized for timezone? = %s\n", ctime(&t)));
+
+ d({
+ char *tmp;
+ tmp = camel_header_format_date(t, offset);
+ printf(" encoded again: %s\n", tmp);
+ g_free(tmp);
+ });
+
+ if (saveoffset)
+ *saveoffset = offset;
+
+ return t;
+}
+
+char *
+camel_header_location_decode(const char *in)
+{
+ int quote = 0;
+ GString *out = g_string_new("");
+ char c, *res;
+
+ /* Sigh. RFC2557 says:
+ * content-location = "Content-Location:" [CFWS] URI [CFWS]
+ * where URI is restricted to the syntax for URLs as
+ * defined in Uniform Resource Locators [URL] until
+ * IETF specifies other kinds of URIs.
+ *
+ * But Netscape puts quotes around the URI when sending web
+ * pages.
+ *
+ * Which is required as defined in rfc2017 [3.1]. Although
+ * outlook doesn't do this.
+ *
+ * Since we get headers already unfolded, we need just drop
+ * all whitespace. URL's cannot contain whitespace or quoted
+ * characters, even when included in quotes.
+ */
+
+ header_decode_lwsp(&in);
+ if (*in == '"') {
+ in++;
+ quote = 1;
+ }
+
+ while ( (c = *in++) ) {
+ if (quote && c=='"')
+ break;
+ if (!camel_mime_is_lwsp(c))
+ g_string_append_c(out, c);
+ }
+
+ res = g_strdup(out->str);
+ g_string_free(out, TRUE);
+
+ return res;
+}
+
+/* extra rfc checks */
+#define CHECKS
+
+#ifdef CHECKS
+static void
+check_header(struct _camel_header_raw *h)
+{
+ unsigned char *p;
+
+ p = h->value;
+ while (p && *p) {
+ if (!isascii(*p)) {
+ w(g_warning("Appending header violates rfc: %s: %s", h->name, h->value));
+ return;
+ }
+ p++;
+ }
+}
+#endif
+
+void
+camel_header_raw_append_parse(struct _camel_header_raw **list, const char *header, int offset)
+{
+ register const char *in;
+ size_t fieldlen;
+ char *name;
+
+ in = header;
+ while (camel_mime_is_fieldname(*in) || *in==':')
+ in++;
+ fieldlen = in-header-1;
+ while (camel_mime_is_lwsp(*in))
+ in++;
+ if (fieldlen == 0 || header[fieldlen] != ':') {
+ printf("Invalid header line: '%s'\n", header);
+ return;
+ }
+ name = g_alloca (fieldlen + 1);
+ memcpy(name, header, fieldlen);
+ name[fieldlen] = 0;
+
+ camel_header_raw_append(list, name, in, offset);
+}
+
+void
+camel_header_raw_append(struct _camel_header_raw **list, const char *name, const char *value, int offset)
+{
+ struct _camel_header_raw *l, *n;
+
+ d(printf("Header: %s: %s\n", name, value));
+
+ n = g_malloc(sizeof(*n));
+ n->next = NULL;
+ n->name = g_strdup(name);
+ n->value = g_strdup(value);
+ n->offset = offset;
+#ifdef CHECKS
+ check_header(n);
+#endif
+ l = (struct _camel_header_raw *)list;
+ while (l->next) {
+ l = l->next;
+ }
+ l->next = n;
+
+ /* debug */
+#if 0
+ if (!strcasecmp(name, "To")) {
+ printf("- Decoding To\n");
+ camel_header_to_decode(value);
+ } else if (!strcasecmp(name, "Content-type")) {
+ printf("- Decoding content-type\n");
+ camel_content_type_dump(camel_content_type_decode(value));
+ } else if (!strcasecmp(name, "MIME-Version")) {
+ printf("- Decoding mime version\n");
+ camel_header_mime_decode(value);
+ }
+#endif
+}
+
+static struct _camel_header_raw *
+header_raw_find_node(struct _camel_header_raw **list, const char *name)
+{
+ struct _camel_header_raw *l;
+
+ l = *list;
+ while (l) {
+ if (!strcasecmp(l->name, name))
+ break;
+ l = l->next;
+ }
+ return l;
+}
+
+const char *
+camel_header_raw_find(struct _camel_header_raw **list, const char *name, int *offset)
+{
+ struct _camel_header_raw *l;
+
+ l = header_raw_find_node(list, name);
+ if (l) {
+ if (offset)
+ *offset = l->offset;
+ return l->value;
+ } else
+ return NULL;
+}
+
+const char *
+camel_header_raw_find_next(struct _camel_header_raw **list, const char *name, int *offset, const char *last)
+{
+ struct _camel_header_raw *l;
+
+ if (last == NULL || name == NULL)
+ return NULL;
+
+ l = *list;
+ while (l && l->value != last)
+ l = l->next;
+ return camel_header_raw_find(&l, name, offset);
+}
+
+static void
+header_raw_free(struct _camel_header_raw *l)
+{
+ g_free(l->name);
+ g_free(l->value);
+ g_free(l);
+}
+
+void
+camel_header_raw_remove(struct _camel_header_raw **list, const char *name)
+{
+ struct _camel_header_raw *l, *p;
+
+ /* the next pointer is at the head of the structure, so this is safe */
+ p = (struct _camel_header_raw *)list;
+ l = *list;
+ while (l) {
+ if (!strcasecmp(l->name, name)) {
+ p->next = l->next;
+ header_raw_free(l);
+ l = p->next;
+ } else {
+ p = l;
+ l = l->next;
+ }
+ }
+}
+
+void
+camel_header_raw_replace(struct _camel_header_raw **list, const char *name, const char *value, int offset)
+{
+ camel_header_raw_remove(list, name);
+ camel_header_raw_append(list, name, value, offset);
+}
+
+void
+camel_header_raw_clear(struct _camel_header_raw **list)
+{
+ struct _camel_header_raw *l, *n;
+ l = *list;
+ while (l) {
+ n = l->next;
+ header_raw_free(l);
+ l = n;
+ }
+ *list = NULL;
+}
+
+char *
+camel_header_msgid_generate (void)
+{
+ static pthread_mutex_t count_lock = PTHREAD_MUTEX_INITIALIZER;
+#define COUNT_LOCK() pthread_mutex_lock (&count_lock)
+#define COUNT_UNLOCK() pthread_mutex_unlock (&count_lock)
+ char host[MAXHOSTNAMELEN];
+ char *name;
+ static int count = 0;
+ char *msgid;
+ int retval;
+ struct addrinfo *ai = NULL, hints = { 0 };
+
+ retval = gethostname (host, sizeof (host));
+ if (retval == 0 && *host) {
+ hints.ai_flags = AI_CANONNAME;
+ ai = camel_getaddrinfo(host, NULL, &hints, NULL);
+ if (ai && ai->ai_canonname)
+ name = ai->ai_canonname;
+ else
+ name = host;
+ } else
+ name = "localhost.localdomain";
+
+ COUNT_LOCK ();
+ msgid = g_strdup_printf ("%d.%d.%d.camel@%s", (int) time (NULL), getpid (), count++, name);
+ COUNT_UNLOCK ();
+
+ if (ai)
+ camel_freeaddrinfo(ai);
+
+ return msgid;
+}
+
+
+static struct {
+ char *name;
+ char *pattern;
+ regex_t regex;
+} mail_list_magic[] = {
+ /* List-Post: <mailto:gnome-hackers@gnome.org> */
+ /* List-Post: <mailto:gnome-hackers> */
+ { "List-Post", "[ \t]*<mailto:([^@>]+)@?([^ \n\t\r>]*)" },
+ /* List-Id: GNOME stuff <gnome-hackers.gnome.org> */
+ /* List-Id: <gnome-hackers.gnome.org> */
+ /* List-Id: <gnome-hackers> */
+ /* This old one wasn't very useful: { "List-Id", " *([^<]+)" },*/
+ { "List-Id", "[^<]*<([^\\.>]+)\\.?([^ \n\t\r>]*)" },
+ /* Mailing-List: list gnome-hackers@gnome.org; contact gnome-hackers-owner@gnome.org */
+ { "Mailing-List", "[ \t]*list ([^@]+)@?([^ \n\t\r>;]*)" },
+ /* Originator: gnome-hackers@gnome.org */
+ { "Originator", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" },
+ /* X-Mailing-List: <gnome-hackers@gnome.org> arcive/latest/100 */
+ /* X-Mailing-List: gnome-hackers@gnome.org */
+ /* X-Mailing-List: gnome-hackers */
+ /* X-Mailing-List: <gnome-hackers> */
+ { "X-Mailing-List", "[ \t]*<?([^@>]+)@?([^ \n\t\r>]*)" },
+ /* X-Loop: gnome-hackers@gnome.org */
+ { "X-Loop", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" },
+ /* X-List: gnome-hackers */
+ /* X-List: gnome-hackers@gnome.org */
+ { "X-List", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" },
+ /* Sender: owner-gnome-hackers@gnome.org */
+ /* Sender: owner-gnome-hacekrs */
+ { "Sender", "[ \t]*owner-([^@]+)@?([^ @\n\t\r>]*)" },
+ /* Sender: gnome-hackers-owner@gnome.org */
+ /* Sender: gnome-hackers-owner */
+ { "Sender", "[ \t]*([^@]+)-owner@?([^ @\n\t\r>]*)" },
+ /* Delivered-To: mailing list gnome-hackers@gnome.org */
+ /* Delivered-To: mailing list gnome-hackers */
+ { "Delivered-To", "[ \t]*mailing list ([^@]+)@?([^ \n\t\r>]*)" },
+ /* Sender: owner-gnome-hackers@gnome.org */
+ /* Sender: <owner-gnome-hackers@gnome.org> */
+ /* Sender: owner-gnome-hackers */
+ /* Sender: <owner-gnome-hackers> */
+ { "Return-Path", "[ \t]*<?owner-([^@>]+)@?([^ \n\t\r>]*)" },
+ /* X-BeenThere: gnome-hackers@gnome.org */
+ /* X-BeenThere: gnome-hackers */
+ { "X-BeenThere", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" },
+ /* List-Unsubscribe: <mailto:gnome-hackers-unsubscribe@gnome.org> */
+ { "List-Unsubscribe", "<mailto:(.+)-unsubscribe@([^ \n\t\r>]*)" },
+};
+
+char *
+camel_header_raw_check_mailing_list(struct _camel_header_raw **list)
+{
+ const char *v;
+ regmatch_t match[3];
+ int i;
+
+ for (i = 0; i < sizeof (mail_list_magic) / sizeof (mail_list_magic[0]); i++) {
+ v = camel_header_raw_find (list, mail_list_magic[i].name, NULL);
+ if (v != NULL && regexec (&mail_list_magic[i].regex, v, 3, match, 0) == 0 && match[1].rm_so != -1) {
+ char *list;
+ int len1, len2;
+
+ len1 = match[1].rm_eo - match[1].rm_so;
+ len2 = match[2].rm_eo - match[2].rm_so;
+
+ list = g_malloc(len1+len2+2);
+ memcpy(list, v + match[1].rm_so, len1);
+ if (len2) {
+ list[len1] = '@';
+ memcpy(list+len1+1, v+match[2].rm_so, len2);
+ list[len1+len2+1]=0;
+ } else {
+ list[len1] = 0;
+ }
+
+ return list;
+ }
+ }
+
+ return NULL;
+}
+
+/* ok, here's the address stuff, what a mess ... */
+struct _camel_header_address *
+camel_header_address_new (void)
+{
+ struct _camel_header_address *h;
+ h = g_malloc0(sizeof(*h));
+ h->type = CAMEL_HEADER_ADDRESS_NONE;
+ h->refcount = 1;
+ return h;
+}
+
+struct _camel_header_address *
+camel_header_address_new_name(const char *name, const char *addr)
+{
+ struct _camel_header_address *h;
+ h = camel_header_address_new();
+ h->type = CAMEL_HEADER_ADDRESS_NAME;
+ h->name = g_strdup(name);
+ h->v.addr = g_strdup(addr);
+ return h;
+}
+
+struct _camel_header_address *
+camel_header_address_new_group (const char *name)
+{
+ struct _camel_header_address *h;
+
+ h = camel_header_address_new();
+ h->type = CAMEL_HEADER_ADDRESS_GROUP;
+ h->name = g_strdup(name);
+ return h;
+}
+
+void
+camel_header_address_ref(struct _camel_header_address *h)
+{
+ if (h)
+ h->refcount++;
+}
+
+void
+camel_header_address_unref(struct _camel_header_address *h)
+{
+ if (h) {
+ if (h->refcount <= 1) {
+ if (h->type == CAMEL_HEADER_ADDRESS_GROUP) {
+ camel_header_address_list_clear(&h->v.members);
+ } else if (h->type == CAMEL_HEADER_ADDRESS_NAME) {
+ g_free(h->v.addr);
+ }
+ g_free(h->name);
+ g_free(h);
+ } else {
+ h->refcount--;
+ }
+ }
+}
+
+void
+camel_header_address_set_name(struct _camel_header_address *h, const char *name)
+{
+ if (h) {
+ g_free(h->name);
+ h->name = g_strdup(name);
+ }
+}
+
+void
+camel_header_address_set_addr(struct _camel_header_address *h, const char *addr)
+{
+ if (h) {
+ if (h->type == CAMEL_HEADER_ADDRESS_NAME
+ || h->type == CAMEL_HEADER_ADDRESS_NONE) {
+ h->type = CAMEL_HEADER_ADDRESS_NAME;
+ g_free(h->v.addr);
+ h->v.addr = g_strdup(addr);
+ } else {
+ g_warning("Trying to set the address on a group");
+ }
+ }
+}
+
+void
+camel_header_address_set_members(struct _camel_header_address *h, struct _camel_header_address *group)
+{
+ if (h) {
+ if (h->type == CAMEL_HEADER_ADDRESS_GROUP
+ || h->type == CAMEL_HEADER_ADDRESS_NONE) {
+ h->type = CAMEL_HEADER_ADDRESS_GROUP;
+ camel_header_address_list_clear(&h->v.members);
+ /* should this ref them? */
+ h->v.members = group;
+ } else {
+ g_warning("Trying to set the members on a name, not group");
+ }
+ }
+}
+
+void
+camel_header_address_add_member(struct _camel_header_address *h, struct _camel_header_address *member)
+{
+ if (h) {
+ if (h->type == CAMEL_HEADER_ADDRESS_GROUP
+ || h->type == CAMEL_HEADER_ADDRESS_NONE) {
+ h->type = CAMEL_HEADER_ADDRESS_GROUP;
+ camel_header_address_list_append(&h->v.members, member);
+ }
+ }
+}
+
+void
+camel_header_address_list_append_list(struct _camel_header_address **l, struct _camel_header_address **h)
+{
+ if (l) {
+ struct _camel_header_address *n = (struct _camel_header_address *)l;
+
+ while (n->next)
+ n = n->next;
+ n->next = *h;
+ }
+}
+
+
+void
+camel_header_address_list_append(struct _camel_header_address **l, struct _camel_header_address *h)
+{
+ if (h) {
+ camel_header_address_list_append_list(l, &h);
+ h->next = NULL;
+ }
+}
+
+void
+camel_header_address_list_clear(struct _camel_header_address **l)
+{
+ struct _camel_header_address *a, *n;
+ a = *l;
+ while (a) {
+ n = a->next;
+ camel_header_address_unref(a);
+ a = n;
+ }
+ *l = NULL;
+}
+
+/* if encode is true, then the result is suitable for mailing, otherwise
+ the result is suitable for display only (and may not even be re-parsable) */
+static void
+header_address_list_encode_append (GString *out, int encode, struct _camel_header_address *a)
+{
+ char *text;
+
+ while (a) {
+ switch (a->type) {
+ case CAMEL_HEADER_ADDRESS_NAME:
+ if (encode)
+ text = camel_header_encode_phrase (a->name);
+ else
+ text = a->name;
+ if (text && *text)
+ g_string_append_printf (out, "%s <%s>", text, a->v.addr);
+ else
+ g_string_append (out, a->v.addr);
+ if (encode)
+ g_free (text);
+ break;
+ case CAMEL_HEADER_ADDRESS_GROUP:
+ if (encode)
+ text = camel_header_encode_phrase (a->name);
+ else
+ text = a->name;
+ g_string_append_printf (out, "%s: ", text);
+ header_address_list_encode_append (out, encode, a->v.members);
+ g_string_append_printf (out, ";");
+ if (encode)
+ g_free (text);
+ break;
+ default:
+ g_warning ("Invalid address type");
+ break;
+ }
+ a = a->next;
+ if (a)
+ g_string_append (out, ", ");
+ }
+}
+
+char *
+camel_header_address_list_encode (struct _camel_header_address *a)
+{
+ GString *out;
+ char *ret;
+
+ if (a == NULL)
+ return NULL;
+
+ out = g_string_new ("");
+ header_address_list_encode_append (out, TRUE, a);
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ return ret;
+}
+
+char *
+camel_header_address_list_format (struct _camel_header_address *a)
+{
+ GString *out;
+ char *ret;
+
+ if (a == NULL)
+ return NULL;
+
+ out = g_string_new ("");
+
+ header_address_list_encode_append (out, FALSE, a);
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ return ret;
+}
+
+char *
+camel_header_address_fold (const char *in, size_t headerlen)
+{
+ size_t len, outlen;
+ const char *inptr = in, *space, *p, *n;
+ GString *out;
+ char *ret;
+ int i, needunfold = FALSE;
+
+ if (in == NULL)
+ return NULL;
+
+ /* first, check to see if we even need to fold */
+ len = headerlen + 2;
+ p = in;
+ while (*p) {
+ n = strchr (p, '\n');
+ if (n == NULL) {
+ len += strlen (p);
+ break;
+ }
+
+ needunfold = TRUE;
+ len += n-p;
+
+ if (len >= CAMEL_FOLD_SIZE)
+ break;
+ len = 0;
+ p = n + 1;
+ }
+ if (len < CAMEL_FOLD_SIZE)
+ return g_strdup (in);
+
+ /* we need to fold, so first unfold (if we need to), then process */
+ if (needunfold)
+ inptr = in = camel_header_unfold (in);
+
+ out = g_string_new ("");
+ outlen = headerlen + 2;
+ while (*inptr) {
+ space = strchr (inptr, ' ');
+ if (space) {
+ len = space - inptr + 1;
+ } else {
+ len = strlen (inptr);
+ }
+
+ d(printf("next word '%.*s'\n", len, inptr));
+
+ if (outlen + len > CAMEL_FOLD_SIZE) {
+ d(printf("outlen = %d wordlen = %d\n", outlen, len));
+ /* strip trailing space */
+ if (out->len > 0 && out->str[out->len-1] == ' ')
+ g_string_truncate (out, out->len-1);
+ g_string_append (out, "\n\t");
+ outlen = 1;
+ }
+
+ outlen += len;
+ for (i = 0; i < len; i++) {
+ g_string_append_c (out, inptr[i]);
+ }
+
+ inptr += len;
+ }
+ ret = out->str;
+ g_string_free (out, FALSE);
+
+ if (needunfold)
+ g_free ((char *)in);
+
+ return ret;
+}
+
+/* simple header folding */
+/* will work even if the header is already folded */
+char *
+camel_header_fold(const char *in, size_t headerlen)
+{
+ size_t len, outlen, i;
+ const char *inptr = in, *space, *p, *n;
+ GString *out;
+ char *ret;
+ int needunfold = FALSE;
+
+ if (in == NULL)
+ return NULL;
+
+ /* first, check to see if we even need to fold */
+ len = headerlen + 2;
+ p = in;
+ while (*p) {
+ n = strchr(p, '\n');
+ if (n == NULL) {
+ len += strlen (p);
+ break;
+ }
+
+ needunfold = TRUE;
+ len += n-p;
+
+ if (len >= CAMEL_FOLD_SIZE)
+ break;
+ len = 0;
+ p = n + 1;
+ }
+ if (len < CAMEL_FOLD_SIZE)
+ return g_strdup(in);
+
+ /* we need to fold, so first unfold (if we need to), then process */
+ if (needunfold)
+ inptr = in = camel_header_unfold(in);
+
+ out = g_string_new("");
+ outlen = headerlen+2;
+ while (*inptr) {
+ space = strchr(inptr, ' ');
+ if (space) {
+ len = space-inptr+1;
+ } else {
+ len = strlen(inptr);
+ }
+ d(printf("next word '%.*s'\n", len, inptr));
+ if (outlen + len > CAMEL_FOLD_SIZE) {
+ d(printf("outlen = %d wordlen = %d\n", outlen, len));
+ /* strip trailing space */
+ if (out->len > 0 && out->str[out->len-1] == ' ')
+ g_string_truncate(out, out->len-1);
+ g_string_append(out, "\n\t");
+ outlen = 1;
+ /* check for very long words, just cut them up */
+ while (outlen+len > CAMEL_FOLD_MAX_SIZE) {
+ for (i=0;i<CAMEL_FOLD_MAX_SIZE-outlen;i++)
+ g_string_append_c(out, inptr[i]);
+ inptr += CAMEL_FOLD_MAX_SIZE-outlen;
+ len -= CAMEL_FOLD_MAX_SIZE-outlen;
+ g_string_append(out, "\n\t");
+ outlen = 1;
+ }
+ }
+ outlen += len;
+ for (i=0;i<len;i++) {
+ g_string_append_c(out, inptr[i]);
+ }
+ inptr += len;
+ }
+ ret = out->str;
+ g_string_free(out, FALSE);
+
+ if (needunfold)
+ g_free((char *)in);
+
+ return ret;
+}
+
+char *
+camel_header_unfold(const char *in)
+{
+ char *out = g_malloc(strlen(in)+1);
+ const char *inptr = in;
+ char c, *o = out;
+
+ o = out;
+ while ((c = *inptr++)) {
+ if (c == '\n') {
+ if (camel_mime_is_lwsp(*inptr)) {
+ do {
+ inptr++;
+ } while (camel_mime_is_lwsp(*inptr));
+ *o++ = ' ';
+ } else {
+ *o++ = c;
+ }
+ } else {
+ *o++ = c;
+ }
+ }
+ *o = 0;
+
+ return out;
+}
+
+void
+camel_mime_utils_init(void)
+{
+ int i, errcode, regex_compilation_failed=0;
+
+ /* Init tables */
+ header_decode_init();
+ base64_init();
+
+ /* precompile regex's for speed at runtime */
+ for (i = 0; i < G_N_ELEMENTS (mail_list_magic); i++) {
+ errcode = regcomp(&mail_list_magic[i].regex, mail_list_magic[i].pattern, REG_EXTENDED|REG_ICASE);
+ if (errcode != 0) {
+ char *errstr;
+ size_t len;
+
+ len = regerror(errcode, &mail_list_magic[i].regex, NULL, 0);
+ errstr = g_malloc0(len + 1);
+ regerror(errcode, &mail_list_magic[i].regex, errstr, len);
+
+ g_warning("Internal error, compiling regex failed: %s: %s", mail_list_magic[i].pattern, errstr);
+ g_free(errstr);
+ regex_compilation_failed++;
+ }
+ }
+
+ g_assert(regex_compilation_failed == 0);
+}
+
+
+void
+camel_mime_utils_shutdown (void)
+{
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (mail_list_magic); i++)
+ regfree (&mail_list_magic[i].regex);
+}
diff --git a/camel/camel-sasl-digest-md5.c b/camel/camel-sasl-digest-md5.c
index e421bd529..91bbcda70 100644
--- a/camel/camel-sasl-digest-md5.c
+++ b/camel/camel-sasl-digest-md5.c
@@ -623,7 +623,7 @@ compute_response (struct _DigestResponse *resp, const char *passwd, gboolean cli
}
static struct _DigestResponse *
-generate_response (struct _DigestChallenge *challenge, struct hostent *host,
+generate_response (struct _DigestChallenge *challenge, const char *host,
const char *protocol, const char *user, const char *passwd)
{
struct _DigestResponse *resp;
@@ -659,7 +659,7 @@ generate_response (struct _DigestChallenge *challenge, struct hostent *host,
/* create the URI */
uri = g_new0 (struct _DigestURI, 1);
uri->type = g_strdup (protocol);
- uri->host = g_strdup (host->h_name);
+ uri->host = g_strdup (host);
uri->name = NULL;
resp->uri = uri;
@@ -795,10 +795,10 @@ digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
struct _param *rspauth;
GByteArray *ret = NULL;
gboolean abort = FALSE;
- struct hostent *h;
const char *ptr;
guchar out[33];
char *tokens;
+ struct addrinfo *ai, hints;
/* Need to wait for the server */
if (!token)
@@ -829,12 +829,20 @@ digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
"\"Quality of Protection\" token\n"));
return NULL;
}
-
- h = camel_service_gethost (sasl->service, ex);
- priv->response = generate_response (priv->challenge, h, sasl->service_name,
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+ ai = camel_getaddrinfo(sasl->service->url->host?sasl->service->url->host:"localhost", NULL, &hints, NULL);
+ if (ai && ai->ai_canonname)
+ ptr = ai->ai_canonname;
+ else
+ ptr = "localhost.localdomain";
+
+ priv->response = generate_response (priv->challenge, ptr, sasl->service_name,
sasl->service->url->user,
sasl->service->url->passwd);
- camel_free_host(h);
+ if (ai)
+ camel_freeaddrinfo(ai);
ret = digest_response (priv->response);
break;
diff --git a/camel/camel-sasl-gssapi.c b/camel/camel-sasl-gssapi.c
new file mode 100644
index 000000000..1efbefee1
--- /dev/null
+++ b/camel/camel-sasl-gssapi.c
@@ -0,0 +1,346 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2003 Ximian, Inc. (www.ximian.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.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_KRB5
+
+#include <string.h>
+#ifdef HAVE_ET_COM_ERR_H
+#include <et/com_err.h>
+#else
+#include <com_err.h>
+#endif
+#ifdef HAVE_MIT_KRB5
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_generic.h>
+#else /* HAVE_HEIMDAL_KRB5 */
+#include <gssapi.h>
+#define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE
+#endif
+#include <errno.h>
+
+#ifndef GSS_C_OID_KRBV5_DES
+#define GSS_C_OID_KRBV5_DES GSS_C_NO_OID
+#endif
+
+#include "camel-sasl-gssapi.h"
+
+CamelServiceAuthType camel_sasl_gssapi_authtype = {
+ N_("GSSAPI"),
+
+ N_("This option will connect to the server using "
+ "Kerberos 5 authentication."),
+
+ "GSSAPI",
+ FALSE
+};
+
+enum {
+ GSSAPI_STATE_INIT,
+ GSSAPI_STATE_CONTINUE_NEEDED,
+ GSSAPI_STATE_COMPLETE,
+ GSSAPI_STATE_AUTHENTICATED
+};
+
+#define GSSAPI_SECURITY_LAYER_NONE (1 << 0)
+#define GSSAPI_SECURITY_LAYER_INTEGRITY (1 << 1)
+#define GSSAPI_SECURITY_LAYER_PRIVACY (1 << 2)
+
+#define DESIRED_SECURITY_LAYER GSSAPI_SECURITY_LAYER_NONE
+
+struct _CamelSaslGssapiPrivate {
+ int state;
+ gss_ctx_id_t ctx;
+ gss_name_t target;
+};
+
+
+static GByteArray *gssapi_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex);
+
+
+static CamelSaslClass *parent_class = NULL;
+
+
+static void
+camel_sasl_gssapi_class_init (CamelSaslGssapiClass *klass)
+{
+ CamelSaslClass *camel_sasl_class = CAMEL_SASL_CLASS (klass);
+
+ parent_class = CAMEL_SASL_CLASS (camel_type_get_global_classfuncs (camel_sasl_get_type ()));
+
+ /* virtual method overload */
+ camel_sasl_class->challenge = gssapi_challenge;
+}
+
+static void
+camel_sasl_gssapi_init (gpointer object, gpointer klass)
+{
+ CamelSaslGssapi *gssapi = CAMEL_SASL_GSSAPI (object);
+
+ gssapi->priv = g_new (struct _CamelSaslGssapiPrivate, 1);
+ gssapi->priv->state = GSSAPI_STATE_INIT;
+ gssapi->priv->ctx = GSS_C_NO_CONTEXT;
+ gssapi->priv->target = GSS_C_NO_NAME;
+}
+
+static void
+camel_sasl_gssapi_finalize (CamelObject *object)
+{
+ CamelSaslGssapi *gssapi = CAMEL_SASL_GSSAPI (object);
+ guint32 status;
+
+ if (gssapi->priv->ctx != GSS_C_NO_CONTEXT)
+ gss_delete_sec_context (&status, &gssapi->priv->ctx, GSS_C_NO_BUFFER);
+
+ if (gssapi->priv->target != GSS_C_NO_NAME)
+ gss_release_name (&status, &gssapi->priv->target);
+
+ g_free (gssapi->priv);
+}
+
+
+CamelType
+camel_sasl_gssapi_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (
+ camel_sasl_get_type (),
+ "CamelSaslGssapi",
+ sizeof (CamelSaslGssapi),
+ sizeof (CamelSaslGssapiClass),
+ (CamelObjectClassInitFunc) camel_sasl_gssapi_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_sasl_gssapi_init,
+ (CamelObjectFinalizeFunc) camel_sasl_gssapi_finalize);
+ }
+
+ return type;
+}
+
+static void
+gssapi_set_exception (OM_uint32 major, OM_uint32 minor, CamelException *ex)
+{
+ const char *str;
+
+ switch (major) {
+ case GSS_S_BAD_MECH:
+ str = _("The specified mechanism is not supported by the "
+ "provided credential, or is unrecognized by the "
+ "implementation.");
+ break;
+ case GSS_S_BAD_NAME:
+ str = _("The provided target_name parameter was ill-formed.");
+ break;
+ case GSS_S_BAD_NAMETYPE:
+ str = _("The provided target_name parameter contained an "
+ "invalid or unsupported type of name.");
+ break;
+ case GSS_S_BAD_BINDINGS:
+ str = _("The input_token contains different channel "
+ "bindings to those specified via the "
+ "input_chan_bindings parameter.");
+ break;
+ case GSS_S_BAD_SIG:
+ str = _("The input_token contains an invalid signature, or a "
+ "signature that could not be verified.");
+ break;
+ case GSS_S_NO_CRED:
+ str = _("The supplied credentials were not valid for context "
+ "initiation, or the credential handle did not "
+ "reference any credentials.");
+ break;
+ case GSS_S_NO_CONTEXT:
+ str = _("The supplied context handle did not refer to a valid context.");
+ break;
+ case GSS_S_DEFECTIVE_TOKEN:
+ str = _("The consistency checks performed on the input_token failed.");
+ break;
+ case GSS_S_DEFECTIVE_CREDENTIAL:
+ str = _("The consistency checks performed on the credential failed.");
+ break;
+ case GSS_S_CREDENTIALS_EXPIRED:
+ str = _("The referenced credentials have expired.");
+ break;
+ case GSS_S_FAILURE:
+ str = error_message (minor);
+ break;
+ default:
+ str = _("Bad authentication response from server.");
+ }
+
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, str);
+}
+
+static GByteArray *
+gssapi_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
+{
+ struct _CamelSaslGssapiPrivate *priv = CAMEL_SASL_GSSAPI (sasl)->priv;
+ OM_uint32 major, minor, flags, time;
+ gss_buffer_desc inbuf, outbuf;
+ GByteArray *challenge = NULL;
+ gss_buffer_t input_token;
+ struct hostent *h;
+ int conf_state;
+ gss_qop_t qop;
+ gss_OID mech;
+ char *str;
+ struct addrinfo *ai, hints;
+
+ switch (priv->state) {
+ case GSSAPI_STATE_INIT:
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+ ai = camel_getaddrinfo(sasl->service->url->host?sasl->service->url->host:"localhost", NULL, &hints, ex);
+ if (ai == NULL)
+ return NULL;
+
+ str = g_strdup_printf("%s@%s", sasl->service_name, ai->ai_canonname);
+ camel_freeaddrinfo(ai);
+
+ inbuf.value = str;
+ inbuf.length = strlen (str);
+ major = gss_import_name (&minor, &inbuf, gss_nt_service_name, &priv->target);
+ g_free (str);
+
+ if (major != GSS_S_COMPLETE) {
+ gssapi_set_exception (major, minor, ex);
+ return NULL;
+ }
+
+ input_token = GSS_C_NO_BUFFER;
+
+ goto challenge;
+ break;
+ case GSSAPI_STATE_CONTINUE_NEEDED:
+ if (token == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Bad authentication response from server."));
+ return NULL;
+ }
+
+ inbuf.value = token->data;
+ inbuf.length = token->len;
+ input_token = &inbuf;
+
+ challenge:
+ major = gss_init_sec_context (&minor, GSS_C_NO_CREDENTIAL, &priv->ctx, priv->target,
+ GSS_C_OID_KRBV5_DES, GSS_C_MUTUAL_FLAG |
+ GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG,
+ 0, GSS_C_NO_CHANNEL_BINDINGS,
+ input_token, &mech, &outbuf, &flags, &time);
+
+ switch (major) {
+ case GSS_S_COMPLETE:
+ priv->state = GSSAPI_STATE_COMPLETE;
+ break;
+ case GSS_S_CONTINUE_NEEDED:
+ priv->state = GSSAPI_STATE_CONTINUE_NEEDED;
+ break;
+ default:
+ gssapi_set_exception (major, minor, ex);
+ return NULL;
+ }
+
+ challenge = g_byte_array_new ();
+ g_byte_array_append (challenge, outbuf.value, outbuf.length);
+#ifndef HAVE_HEIMDAL_KRB5
+ gss_release_buffer (&minor, &outbuf);
+#endif
+ break;
+ case GSSAPI_STATE_COMPLETE:
+ if (token == NULL) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Bad authentication response from server."));
+ return NULL;
+ }
+
+ inbuf.value = token->data;
+ inbuf.length = token->len;
+
+ major = gss_unwrap (&minor, priv->ctx, &inbuf, &outbuf, &conf_state, &qop);
+ if (major != GSS_S_COMPLETE) {
+ gssapi_set_exception (major, minor, ex);
+ return NULL;
+ }
+
+ if (outbuf.length < 4) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Bad authentication response from server."));
+#ifndef HAVE_HEIMDAL_KRB5
+ gss_release_buffer (&minor, &outbuf);
+#endif
+ return NULL;
+ }
+
+ /* check that our desired security layer is supported */
+ if ((((unsigned char *) outbuf.value)[0] & DESIRED_SECURITY_LAYER) != DESIRED_SECURITY_LAYER) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Unsupported security layer."));
+#ifndef HAVE_HEIMDAL_KRB5
+ gss_release_buffer (&minor, &outbuf);
+#endif
+ return NULL;
+ }
+
+ inbuf.length = 4 + strlen (sasl->service->url->user);
+ inbuf.value = str = g_malloc (inbuf.length);
+ memcpy (inbuf.value, outbuf.value, 4);
+ str[0] = DESIRED_SECURITY_LAYER;
+ memcpy (str + 4, sasl->service->url->user, inbuf.length - 4);
+
+#ifndef HAVE_HEIMDAL_KRB5
+ gss_release_buffer (&minor, &outbuf);
+#endif
+
+ major = gss_wrap (&minor, priv->ctx, FALSE, qop, &inbuf, &conf_state, &outbuf);
+ if (major != GSS_S_COMPLETE) {
+ gssapi_set_exception (major, minor, ex);
+ g_free (str);
+ return NULL;
+ }
+
+ g_free (str);
+ challenge = g_byte_array_new ();
+ g_byte_array_append (challenge, outbuf.value, outbuf.length);
+
+#ifndef HAVE_HEIMDAL_KRB5
+ gss_release_buffer (&minor, &outbuf);
+#endif
+
+ priv->state = GSSAPI_STATE_AUTHENTICATED;
+
+ sasl->authenticated = TRUE;
+ break;
+ default:
+ return NULL;
+ }
+
+ return challenge;
+}
+
+#endif /* HAVE_KRB5 */
diff --git a/camel/camel-sasl-kerberos4.c b/camel/camel-sasl-kerberos4.c
index e225c1a26..fd366e61d 100644
--- a/camel/camel-sasl-kerberos4.c
+++ b/camel/camel-sasl-kerberos4.c
@@ -129,6 +129,7 @@ krb4_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
KTEXT_ST authenticator;
CREDENTIALS credentials;
guint32 plus1;
+ struct addrinfo *ai, hints;
/* Need to wait for the server */
if (!token)
@@ -142,12 +143,17 @@ krb4_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
memcpy (&priv->nonce_n, token->data, 4);
priv->nonce_h = ntohl (priv->nonce_n);
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+ ai = camel_getaddrinfo(sasl->service->url->host?sasl->service->url->host:"localhost", NULL, &hints, ex);
+ if (ai == NULL)
+ goto lose;
+
/* Our response is an authenticator including that number. */
- h = camel_service_gethost (sasl->service, ex);
- inst = g_strndup (h->h_name, strcspn (h->h_name, "."));
+ inst = g_strndup (ai->ai_canonname, strcspn (ai->ai_canonname, "."));
camel_strdown (inst);
- realm = g_strdup (krb_realmofhost (h->h_name));
- camel_free_host(h);
+ realm = g_strdup (krb_realmofhost (ai->ai_canonname));
+ camel_freeaddrinfo(ai);
status = krb_mk_req (&authenticator, sasl->service_name, inst, realm, priv->nonce_h);
if (status == KSUCCESS) {
status = krb_get_cred (sasl->service_name, inst, realm, &credentials);
diff --git a/camel/camel-service.c b/camel/camel-service.c
index d108f124c..31c39614f 100644
--- a/camel/camel-service.c
+++ b/camel/camel-service.c
@@ -34,6 +34,8 @@
#include <pthread.h>
#include <errno.h>
+#include <sys/poll.h>
+
#include "e-util/e-msgport.h"
#include "e-util/e-host-utils.h"
@@ -642,67 +644,226 @@ camel_service_query_auth_types (CamelService *service, CamelException *ex)
return ret;
}
-/* URL utility routines */
-
-/**
- * camel_service_gethost:
- * @service: a CamelService
- * @ex: a CamelException
- *
- * This is a convenience function to do a gethostbyname on the host
- * for the service's URL.
- *
- * Return value: a (statically-allocated) hostent.
- **/
-struct hostent *
-camel_service_gethost (CamelService *service, CamelException *ex)
-{
- char *hostname;
-
- if (service->url->host)
- hostname = service->url->host;
- else
- hostname = "localhost";
-
- return camel_gethostbyname (hostname, ex);
-}
-
-#ifdef offsetof
-#define STRUCT_OFFSET(type, field) ((gint) offsetof (type, field))
-#else
-#define STRUCT_OFFSET(type, field) ((gint) ((gchar*) &((type *) 0)->field))
-#endif
-
-struct _lookup_msg {
+/* ********************************************************************** */
+struct _addrinfo_msg {
EMsg msg;
unsigned int cancelled:1;
+
+ /* for host lookup */
const char *name;
- int len;
- int type;
+ const char *service;
int result;
- int herr;
+ const struct addrinfo *hints;
+ struct addrinfo **res;
+
+ /* for host lookup emulation */
+#ifdef NEED_ADDRINFO
struct hostent hostbuf;
int hostbuflen;
char *hostbufmem;
+#endif
+
+ /* for name lookup */
+ const struct sockaddr *addr;
+ socklen_t addrlen;
+ char *host;
+ int hostlen;
+ char *serv;
+ int servlen;
+ int flags;
};
+static void
+cs_freeinfo(struct _addrinfo_msg *msg)
+{
+ g_free(msg->host);
+ g_free(msg->serv);
+#ifdef NEED_ADDRINFO
+ g_free(msg->hostbufmem);
+#endif
+ g_free(msg);
+}
+
+/* returns -1 if cancelled */
+static int
+cs_waitinfo(void *(worker)(void *), struct _addrinfo_msg *msg, const char *error, CamelException *ex)
+{
+ EMsgPort *reply_port;
+ pthread_t id;
+ int err, cancel_fd, cancel = 0, fd;
+
+ cancel_fd = camel_operation_cancel_fd(NULL);
+ if (cancel_fd == -1) {
+ worker(msg);
+ return 0;
+ }
+
+ reply_port = msg->msg.reply_port = e_msgport_new();
+ fd = e_msgport_fd(msg->msg.reply_port);
+ if ((err = pthread_create(&id, NULL, worker, msg)) == 0) {
+ struct pollfd polls[2];
+ int status;
+
+ polls[0].fd = fd;
+ polls[0].events = POLLIN;
+ polls[1].fd = cancel_fd;
+ polls[1].events = POLLIN;
+
+ d(printf("waiting for name return/cancellation in main process\n"));
+ do {
+ polls[0].revents = 0;
+ polls[1].revents = 0;
+ status = poll(polls, 2, -1);
+ } while (status == -1 && errno == EINTR);
+
+ if (status == -1 || (polls[1].revents & POLLIN)) {
+ if (status == -1)
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s", error, g_strerror(errno));
+ else
+ camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
+
+ /* We cancel so if the thread impl is decent it causes immediate exit.
+ We detach so we dont need to wait for it to exit if it isn't.
+ We check the reply port incase we had a reply in the mean time, which we free later */
+ d(printf("Cancelling lookup thread and leaving it\n"));
+ msg->cancelled = 1;
+ pthread_detach(id);
+ pthread_cancel(id);
+ cancel = 1;
+ } else {
+ struct _addrinfo_msg *reply = (struct _addrinfo_msg *)e_msgport_get(reply_port);
+
+ g_assert(reply == msg);
+ d(printf("waiting for child to exit\n"));
+ pthread_join(id, NULL);
+ d(printf("child done\n"));
+ }
+ } else {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s: %s", _("cannot create thread"), g_strerror(err));
+ }
+ e_msgport_destroy(reply_port);
+
+ return cancel;
+}
+
+#ifdef NEED_ADDRINFO
static void *
-get_hostbyname(void *data)
+cs_getaddrinfo(void *data)
{
- struct _lookup_msg *info = data;
+ struct _addrinfo_msg *msg = data;
+ int herr;
+ struct hostent h;
+ struct addrinfo *res, *last = NULL;
+ struct sockaddr_in *sin;
+ in_port_t port = 0;
+ int i;
+
+ /* This is a pretty simplistic emulation of getaddrinfo */
- while ((info->result = e_gethostbyname_r(info->name, &info->hostbuf, info->hostbufmem, info->hostbuflen, &info->herr)) == ERANGE) {
- d(printf("gethostbyname fialed?\n"));
+ while ((msg->result = e_gethostbyname_r(msg->name, &h, msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) {
pthread_testcancel();
- info->hostbuflen *= 2;
- info->hostbufmem = g_realloc(info->hostbufmem, info->hostbuflen);
+ msg->hostbuflen *= 2;
+ msg->hostbufmem = g_realloc(msg->hostbufmem, msg->hostbuflen);
}
-
- d(printf("gethostbyname ok?\n"));
/* If we got cancelled, dont reply, just free it */
+ if (msg->cancelled)
+ goto cancel;
+
+ /* FIXME: map error numbers across */
+ if (msg->result != 0)
+ goto reply;
+
+ /* check hints matched */
+ if (msg->hints && msg->hints->ai_family && msg->hints->ai_family != h.h_addrtype) {
+ msg->result = EAI_FAMILY;
+ goto reply;
+ }
+
+ /* we only support ipv4 for this interface, even if it could supply ipv6 */
+ if (h.h_addrtype != AF_INET) {
+ msg->result = EAI_FAMILY;
+ goto reply;
+ }
+
+ /* check service mapping */
+ if (msg->service) {
+ const char *p = msg->service;
+
+ while (*p) {
+ if (*p < '0' || *p > '9')
+ break;
+ p++;
+ }
+
+ if (*p) {
+ const char *socktype = NULL;
+ struct servent *serv;
+
+ if (msg->hints && msg->hints->ai_socktype) {
+ if (msg->hints->ai_socktype == SOCK_STREAM)
+ socktype = "tcp";
+ else if (msg->hints->ai_socktype == SOCK_DGRAM)
+ socktype = "udp";
+ }
+
+ serv = getservbyname(msg->service, socktype);
+ if (serv == NULL) {
+ msg->result = EAI_NONAME;
+ goto reply;
+ }
+ port = serv->s_port;
+ } else {
+ port = htons(strtoul(msg->service, NULL, 10));
+ }
+ }
+
+ for (i=0;h.h_addr_list[i];i++) {
+ res = g_malloc0(sizeof(*res));
+ if (msg->hints) {
+ res->ai_flags = msg->hints->ai_flags;
+ if (msg->hints->ai_flags & AI_CANONNAME)
+ res->ai_canonname = g_strdup(h.h_name);
+ res->ai_socktype = msg->hints->ai_socktype;
+ res->ai_protocol = msg->hints->ai_protocol;
+ } else {
+ res->ai_flags = 0;
+ res->ai_socktype = SOCK_STREAM; /* fudge */
+ res->ai_protocol = 0; /* fudge */
+ }
+ res->ai_family = AF_INET;
+ res->ai_addrlen = sizeof(*sin);
+ res->ai_addr = g_malloc(sizeof(*sin));
+ sin = (struct sockaddr_in *)res->ai_addr;
+ sin->sin_family = AF_INET;
+ sin->sin_port = port;
+ memcpy(&sin->sin_addr, h.h_addr_list[i], sizeof(sin->sin_addr));
+
+ if (last == NULL) {
+ *msg->res = last = res;
+ } else {
+ last->ai_next = res;
+ last = res;
+ }
+ }
+reply:
+ e_msgport_reply((EMsg *)msg);
+ return NULL;
+cancel:
+ cs_freeinfo(msg);
+ return NULL;
+}
+#else
+static void *
+cs_getaddrinfo(void *data)
+{
+ struct _addrinfo_msg *info = data;
+
+ do {
+ info->result = getaddrinfo(info->name, info->service, info->hints, info->res);
+ } while (info->result == EAI_AGAIN);
+
if (info->cancelled) {
- g_free(info->hostbufmem);
g_free(info);
} else {
e_msgport_reply((EMsg *)info);
@@ -710,239 +871,186 @@ get_hostbyname(void *data)
return NULL;
}
+#endif /* NEED_ADDRINFO */
-struct hostent *
-camel_gethostbyname (const char *name, CamelException *exout)
+struct addrinfo *
+camel_getaddrinfo(const char *name, const char *service, const struct addrinfo *hints, CamelException *ex)
{
- int fdmax, status, fd, cancel_fd;
- struct _lookup_msg *msg;
- CamelException ex;
-
+ struct _addrinfo_msg *msg;
+ struct addrinfo *res = NULL;
+#ifndef ENABLE_IPv6
+ struct addrinfo *myhints;
+#endif
g_return_val_if_fail(name != NULL, NULL);
if (camel_operation_cancel_check(NULL)) {
- camel_exception_set (exout, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
+ camel_exception_set(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
return NULL;
}
- camel_exception_init(&ex);
camel_operation_start_transient(NULL, _("Resolving: %s"), name);
- msg = g_malloc0(sizeof(*msg));
- msg->hostbuflen = 1024;
- msg->hostbufmem = g_malloc(msg->hostbuflen);
- msg->name = name;
- msg->result = -1;
-
- cancel_fd = camel_operation_cancel_fd(NULL);
- if (cancel_fd == -1) {
- get_hostbyname(msg);
- } else {
- EMsgPort *reply_port;
- pthread_t id;
- fd_set rdset;
- int err;
-
- reply_port = msg->msg.reply_port = e_msgport_new();
- fd = e_msgport_fd(msg->msg.reply_port);
- if ((err = pthread_create(&id, NULL, get_hostbyname, msg)) == 0) {
- d(printf("waiting for name return/cancellation in main process\n"));
- do {
- FD_ZERO(&rdset);
- FD_SET(cancel_fd, &rdset);
- FD_SET(fd, &rdset);
- fdmax = MAX(fd, cancel_fd) + 1;
- status = select(fdmax, &rdset, NULL, 0, NULL);
- } while (status == -1 && errno == EINTR);
-
- if (status == -1 || FD_ISSET(cancel_fd, &rdset)) {
- if (status == -1)
- camel_exception_setv(&ex, CAMEL_EXCEPTION_SYSTEM, _("Failure in name lookup: %s"), g_strerror(errno));
- else
- camel_exception_setv(&ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
-
- /* We cancel so if the thread impl is decent it causes immediate exit.
- We detach so we dont need to wait for it to exit if it isn't.
- We check the reply port incase we had a reply in the mean time, which we free later */
- d(printf("Cancelling lookup thread and leaving it\n"));
- msg->cancelled = 1;
- pthread_detach(id);
- pthread_cancel(id);
- msg = (struct _lookup_msg *)e_msgport_get(reply_port);
- } else {
- struct _lookup_msg *reply = (struct _lookup_msg *)e_msgport_get(reply_port);
-
- g_assert(reply == msg);
- d(printf("waiting for child to exit\n"));
- pthread_join(id, NULL);
- d(printf("child done\n"));
- }
- } else {
- camel_exception_setv(&ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: cannot create thread: %s"), g_strerror(err));
- }
- e_msgport_destroy(reply_port);
+ /* force ipv4 addresses only */
+#ifndef ENABLE_IPv6
+ if (hints == NULL) {
+ memset(&myhints, 0, sizeof(myhints));
+ hints = &myhints;
}
-
- camel_operation_end(NULL);
-
- if (!camel_exception_is_set(&ex)) {
- if (msg->result == 0)
- return &msg->hostbuf;
- if (msg->herr == HOST_NOT_FOUND || msg->herr == NO_DATA)
- camel_exception_setv (&ex, CAMEL_EXCEPTION_SYSTEM,
- _("Host lookup failed: %s: host not found"), name);
- else
- camel_exception_setv (&ex, CAMEL_EXCEPTION_SYSTEM,
- _("Host lookup failed: %s: unknown reason"), name);
- }
+ hints->ai_family = AF_INET;
+#endif
- if (msg) {
- g_free(msg->hostbufmem);
- g_free(msg);
- }
+ msg = g_malloc0(sizeof(*msg));
+ msg->name = name;
+ msg->service = service;
+ msg->hints = hints;
+ msg->res = &res;
+#ifdef NEED_ADDRINFO
+ msg->hostbuflen = 1024;
+ msg->hostbufmem = g_malloc(msg->hostbuflen);
+#endif
+ if (cs_waitinfo(cs_getaddrinfo, msg, _("Host lookup failed"), ex) == 0)
+ cs_freeinfo(msg);
+ else
+ res = NULL;
- camel_exception_xfer(exout, &ex);
+ camel_operation_end(NULL);
- return NULL;
+ return res;
}
-static void *
-get_hostbyaddr (void *data)
+void
+camel_freeaddrinfo(struct addrinfo *host)
{
- struct _lookup_msg *info = data;
-
- while ((info->result = e_gethostbyaddr_r (info->name, info->len, info->type, &info->hostbuf,
- info->hostbufmem, info->hostbuflen, &info->herr)) == ERANGE) {
- d(printf ("gethostbyaddr fialed?\n"));
- pthread_testcancel ();
- info->hostbuflen *= 2;
- info->hostbufmem = g_realloc (info->hostbufmem, info->hostbuflen);
+#ifdef NEED_ADDRINFO
+ while (host) {
+ struct addrinfo *next = host->ai_next;
+
+ g_free(host->ai_canonname);
+ g_free(host->ai_addr);
+ g_free(host);
+ host = next;
}
-
- d(printf ("gethostbyaddr ok?\n"));
-
- if (info->cancelled) {
- g_free(info->hostbufmem);
- g_free(info);
- } else {
- e_msgport_reply((EMsg *)info);
- }
-
- return NULL;
+#else
+ freeaddrinfo(host);
+#endif
}
-
-struct hostent *
-camel_gethostbyaddr (const char *addr, int len, int type, CamelException *exout)
+#ifdef NEED_ADDRINFO
+static void *
+cs_getnameinfo(void *data)
{
- int fdmax, status, fd, cancel_fd;
- struct _lookup_msg *msg;
- CamelException ex;
+ struct _addrinfo_msg *msg = data;
+ int herr;
+ struct hostent h;
+ struct sockaddr_in *sin = (struct sockaddr_in *)msg->addr;
- g_return_val_if_fail (addr != NULL, NULL);
-
- if (camel_operation_cancel_check (NULL)) {
- camel_exception_set (exout, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
+ /* FIXME: error code */
+ if (msg->addr->sa_family != AF_INET) {
+ msg->result = -1;
return NULL;
}
- camel_exception_init(&ex);
- camel_operation_start_transient (NULL, _("Resolving address"));
-
- msg = g_malloc0 (sizeof (struct _lookup_msg));
- msg->hostbuflen = 1024;
- msg->hostbufmem = g_malloc (msg->hostbuflen);
- msg->name = addr;
- msg->len = len;
- msg->type = type;
- msg->result = -1;
-
- cancel_fd = camel_operation_cancel_fd (NULL);
- if (cancel_fd == -1) {
- get_hostbyaddr (msg);
- } else {
- EMsgPort *reply_port;
- pthread_t id;
- fd_set rdset;
- int err;
-
- reply_port = msg->msg.reply_port = e_msgport_new ();
- fd = e_msgport_fd (msg->msg.reply_port);
- if ((err = pthread_create (&id, NULL, get_hostbyaddr, msg)) == 0) {
- d(printf("waiting for name return/cancellation in main process\n"));
- do {
- FD_ZERO(&rdset);
- FD_SET(cancel_fd, &rdset);
- FD_SET(fd, &rdset);
- fdmax = MAX(fd, cancel_fd) + 1;
- status = select (fdmax, &rdset, NULL, 0, NULL);
- } while (status == -1 && errno == EINTR);
-
- if (status == -1 || FD_ISSET(cancel_fd, &rdset)) {
- if (status == -1)
- camel_exception_setv(&ex, CAMEL_EXCEPTION_SYSTEM, _("Failure in name lookup: %s"), g_strerror(errno));
- else
- camel_exception_setv(&ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
-
- /* We cancel so if the thread impl is decent it causes immediate exit.
- We detach so we dont need to wait for it to exit if it isn't.
- We check the reply port incase we had a reply in the mean time, which we free later */
- d(printf("Cancelling lookup thread and leaving it\n"));
- msg->cancelled = 1;
- pthread_detach(id);
- pthread_cancel(id);
- msg = (struct _lookup_msg *)e_msgport_get(reply_port);
- } else {
- struct _lookup_msg *reply = (struct _lookup_msg *)e_msgport_get(reply_port);
-
- g_assert(reply == msg);
- d(printf("waiting for child to exit\n"));
- pthread_join(id, NULL);
- d(printf("child done\n"));
- }
- } else {
- camel_exception_setv(&ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: cannot create thread: %s"), g_strerror(err));
- }
+ /* FIXME: honour getnameinfo flags: do we care, not really */
-
- e_msgport_destroy (reply_port);
+ while ((msg->result = e_gethostbyaddr_r((const char *)&sin->sin_addr, sizeof(sin->sin_addr), AF_INET, &h,
+ msg->hostbufmem, msg->hostbuflen, &herr)) == ERANGE) {
+ pthread_testcancel ();
+ msg->hostbuflen *= 2;
+ msg->hostbufmem = g_realloc(msg->hostbufmem, msg->hostbuflen);
}
- camel_operation_end (NULL);
-
- if (!camel_exception_is_set(&ex)) {
- if (msg->result == 0)
- return &msg->hostbuf;
+ if (msg->cancelled)
+ goto cancel;
- if (msg->herr == HOST_NOT_FOUND || msg->herr == NO_DATA)
- camel_exception_setv (&ex, CAMEL_EXCEPTION_SYSTEM,
- _("Host lookup failed: host not found"));
- else
- camel_exception_setv (&ex, CAMEL_EXCEPTION_SYSTEM,
- _("Host lookup failed: unknown reason"));
+ if (msg->host) {
+ g_free(msg->host);
+ if (msg->result == 0 && h.h_name && h.h_name[0]) {
+ msg->host = g_strdup(h.h_name);
+ } else {
+ unsigned char *in = (unsigned char *)&sin->sin_addr;
+
+ /* sin_addr is always network order which is big-endian */
+ msg->host = g_strdup_printf("%u.%u.%u.%u", in[0], in[1], in[2], in[3]);
+ }
}
- if (msg) {
- g_free(msg->hostbufmem);
- g_free(msg);
- }
+ /* we never actually use this anyway */
+ if (msg->serv)
+ sprintf(msg->serv, "%d", sin->sin_port);
+
+ e_msgport_reply((EMsg *)msg);
+ return NULL;
+cancel:
+ cs_freeinfo(msg);
+ return NULL;
+}
+#else
+static void *
+cs_getnameinfo(void *data)
+{
+ struct _addrinfo_msg *msg = data;
- camel_exception_xfer(exout, &ex);
+ /* there doens't appear to be a return code which says host or serv buffers are too short, lengthen them */
+ do {
+ msg->result = getnameinfo(msg->addr, msg->addrlen, msg->host, msg->hostlen, msg->serv, msg->servlen, msg->flags);
+ } while (msg->result == EAI_AGAIN);
+
+ if (msg->cancelled)
+ cs_freeinfo(msg);
+ else
+ e_msgport_reply((EMsg *)msg);
return NULL;
}
+#endif
-void camel_free_host(struct hostent *h)
+int
+camel_getnameinfo(const struct sockaddr *sa, socklen_t salen, char **host, char **serv, int flags, CamelException *ex)
{
- struct _lookup_msg *msg;
+ struct _addrinfo_msg *msg;
+ int result;
- g_return_if_fail(h != NULL);
+ if (camel_operation_cancel_check(NULL)) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled"));
+ return -1;
+ }
- /* yeah this looks ugly but it is safe. we passed out a reference to inside our structure, this maps it
- to the base structure, so we can free everything right without having to keep track of it separately */
- msg = (struct _lookup_msg *)(((char *)h) - STRUCT_OFFSET(struct _lookup_msg, hostbuf));
+ camel_operation_start_transient(NULL, _("Resolving address"));
- g_free(msg->hostbufmem);
+ msg = g_malloc0(sizeof(*msg));
+ msg->addr = sa;
+ msg->addrlen = salen;
+ if (host) {
+ msg->hostlen = NI_MAXHOST;
+ msg->host = g_malloc(msg->hostlen);
+ msg->host[0] = 0;
+ }
+ if (serv) {
+ msg->servlen = NI_MAXSERV;
+ msg->serv = g_malloc(msg->servlen);
+ msg->serv[0] = 0;
+ }
+ msg->flags = flags;
+#ifdef NEED_ADDRINFO
+ msg->hostbuflen = 1024;
+ msg->hostbufmem = g_malloc(msg->hostbuflen);
+#endif
+ cs_waitinfo(cs_getnameinfo, msg, _("Name lookup failed"), ex);
+
+ result = msg->result;
+
+ if (host)
+ *host = g_strdup(msg->host);
+ if (serv)
+ *serv = g_strdup(msg->serv);
+
+ g_free(msg->host);
+ g_free(msg->serv);
g_free(msg);
+
+ camel_operation_end(NULL);
+
+ return result;
}
+
diff --git a/camel/camel-service.h b/camel/camel-service.h
index 587749e24..f49472cc5 100644
--- a/camel/camel-service.h
+++ b/camel/camel-service.h
@@ -135,14 +135,61 @@ CamelProvider * camel_service_get_provider (CamelService *service);
GList * camel_service_query_auth_types (CamelService *service,
CamelException *ex);
-/* convenience functions */
-struct hostent * camel_service_gethost (CamelService *service,
- CamelException *ex);
+#ifdef NEED_ADDRINFO
+/* Some of this is copied from GNU's netdb.h
+
+ Copyright (C) 1996-2002, 2003, 2004 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+*/
+struct addrinfo {
+ int ai_flags;
+ int ai_family;
+ int ai_socktype;
+ int ai_protocol;
+ size_t ai_addrlen;
+ struct sockaddr *ai_addr;
+ char *ai_canonname;
+ struct addrinfo *ai_next;
+};
-/* cancellable dns lookup */
-struct hostent * camel_gethostbyname (const char *name, CamelException *ex);
-struct hostent * camel_gethostbyaddr (const char *addr, int len, int type, CamelException *ex);
-void camel_free_host (struct hostent *h);
+#define AI_CANONNAME 0x0002 /* Request for canonical name. */
+#define AI_NUMERICHOST 0x0004 /* Don't use name resolution. */
+
+/* Error values for `getaddrinfo' function. */
+#define EAI_BADFLAGS -1 /* Invalid value for `ai_flags' field. */
+#define EAI_NONAME -2 /* NAME or SERVICE is unknown. */
+#define EAI_AGAIN -3 /* Temporary failure in name resolution. */
+#define EAI_FAIL -4 /* Non-recoverable failure in name res. */
+#define EAI_NODATA -5 /* No address associated with NAME. */
+#define EAI_FAMILY -6 /* `ai_family' not supported. */
+#define EAI_SOCKTYPE -7 /* `ai_socktype' not supported. */
+#define EAI_SERVICE -8 /* SERVICE not supported for `ai_socktype'. */
+#define EAI_ADDRFAMILY -9 /* Address family for NAME not supported. */
+#define EAI_MEMORY -10 /* Memory allocation failure. */
+#define EAI_SYSTEM -11 /* System error returned in `errno'. */
+#define EAI_OVERFLOW -12 /* Argument buffer overflow. */
+
+#define NI_MAXHOST 1025
+#define NI_MAXSERV 32
+
+#define NI_NUMERICHOST 1 /* Don't try to look up hostname. */
+#define NI_NUMERICSERV 2 /* Don't convert port number to name. */
+#define NI_NOFQDN 4 /* Only return nodename portion. */
+#define NI_NAMEREQD 8 /* Don't return numeric addresses. */
+#define NI_DGRAM 16 /* Look up UDP service rather than TCP. */
+#endif
+
+/* new hostname interfaces */
+struct addrinfo *camel_getaddrinfo(const char *name, const char *service,
+ const struct addrinfo *hints, CamelException *ex);
+void camel_freeaddrinfo(struct addrinfo *host);
+int camel_getnameinfo(const struct sockaddr *sa, socklen_t salen, char **host, char **serv,
+ int flags, CamelException *ex);
/* Standard Camel function */
CamelType camel_service_get_type (void);
diff --git a/camel/camel-tcp-stream-raw.c b/camel/camel-tcp-stream-raw.c
index 04e47ac64..93f3fbdba 100644
--- a/camel/camel-tcp-stream-raw.c
+++ b/camel/camel-tcp-stream-raw.c
@@ -20,7 +20,6 @@
*
*/
-
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
@@ -48,11 +47,11 @@ static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n);
static int stream_flush (CamelStream *stream);
static int stream_close (CamelStream *stream);
-static int stream_connect (CamelTcpStream *stream, struct hostent *host, int port);
+static int stream_connect (CamelTcpStream *stream, struct addrinfo *host);
static int stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data);
static int stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data);
-static CamelTcpAddress *stream_get_local_address (CamelTcpStream *stream);
-static CamelTcpAddress *stream_get_remote_address (CamelTcpStream *stream);
+static struct sockaddr *stream_get_local_address (CamelTcpStream *stream, socklen_t *len);
+static struct sockaddr *stream_get_remote_address (CamelTcpStream *stream, socklen_t *len);
static void
camel_tcp_stream_raw_class_init (CamelTcpStreamRawClass *camel_tcp_stream_raw_class)
@@ -267,13 +266,8 @@ stream_close (CamelStream *stream)
/* this is a 'cancellable' connect, cancellable from camel_operation_cancel etc */
/* returns -1 & errno == EINTR if the connection was cancelled */
static int
-socket_connect (struct hostent *h, int port)
+socket_connect(struct addrinfo *h)
{
-#ifdef ENABLE_IPv6
- struct sockaddr_in6 sin6;
-#endif
- struct sockaddr_in sin;
- struct sockaddr *saddr;
struct timeval tv;
socklen_t len;
int cancel_fd;
@@ -286,31 +280,17 @@ socket_connect (struct hostent *h, int port)
return -1;
}
- /* setup connect, we do it using a nonblocking socket so we can poll it */
-#ifdef ENABLE_IPv6
- if (h->h_addrtype == AF_INET6) {
- sin6.sin6_port = htons (port);
- sin6.sin6_family = h->h_addrtype;
- memcpy (&sin6.sin6_addr, h->h_addr, sizeof (sin6.sin6_addr));
- saddr = (struct sockaddr *) &sin6;
- len = sizeof (sin6);
- } else {
-#endif
- sin.sin_port = htons (port);
- sin.sin_family = h->h_addrtype;
- memcpy (&sin.sin_addr, h->h_addr, sizeof (sin.sin_addr));
- saddr = (struct sockaddr *) &sin;
- len = sizeof (sin);
-#ifdef ENABLE_IPv6
+ if (h->ai_socktype != SOCK_STREAM) {
+ errno = EINVAL;
+ return -1;
}
-#endif
-
- if ((fd = socket (h->h_addrtype, SOCK_STREAM, 0)) == -1)
+
+ if ((fd = socket (h->ai_family, SOCK_STREAM, 0)) == -1)
return -1;
cancel_fd = camel_operation_cancel_fd (NULL);
if (cancel_fd == -1) {
- if (connect (fd, saddr, len) == -1) {
+ if (connect (fd, h->ai_addr, h->ai_addrlen) == -1) {
errnosav = errno;
close (fd);
errno = errnosav;
@@ -325,7 +305,7 @@ socket_connect (struct hostent *h, int port)
flags = fcntl (fd, F_GETFL);
fcntl (fd, F_SETFL, flags | O_NONBLOCK);
- if (connect (fd, saddr, len) == 0) {
+ if (connect (fd, h->ai_addr, h->ai_addrlen) == 0) {
fcntl (fd, F_SETFL, flags);
return fd;
}
@@ -383,21 +363,22 @@ socket_connect (struct hostent *h, int port)
}
static int
-stream_connect (CamelTcpStream *stream, struct hostent *host, int port)
+stream_connect (CamelTcpStream *stream, struct addrinfo *host)
{
CamelTcpStreamRaw *raw = CAMEL_TCP_STREAM_RAW (stream);
- int fd;
g_return_val_if_fail (host != NULL, -1);
-
- if ((fd = socket_connect (host, port)) == -1)
- return -1;
-
- raw->sockfd = fd;
-
- return 0;
-}
+ while (host) {
+ raw->sockfd = socket_connect(host);
+ if (raw->sockfd != -1)
+ return 0;
+
+ host = host->ai_next;
+ }
+
+ return -1;
+}
static int
get_sockopt_level (const CamelSockOptData *data)
@@ -496,72 +477,42 @@ stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data)
sizeof (data->value));
}
+static struct sockaddr *
+stream_get_local_address (CamelTcpStream *stream, socklen_t *len)
+{
#ifdef ENABLE_IPv6
-#define MIN_SOCKADDR_BUFLEN (sizeof (struct sockaddr_in6))
+ struct sockaddr_in6 sin;
#else
-#define MIN_SOCKADDR_BUFLEN (sizeof (struct sockaddr_in))
+ struct sockaddr_in sin;
#endif
+ struct sockaddr *saddr = (struct sockaddr *)&sin;
-static CamelTcpAddress *
-stream_get_local_address (CamelTcpStream *stream)
-{
- unsigned char buf[MIN_SOCKADDR_BUFLEN];
-#ifdef ENABLE_IPv6
- struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) buf;
-#endif
- struct sockaddr_in *sin = (struct sockaddr_in *) buf;
- struct sockaddr *saddr = (struct sockaddr *) buf;
- gpointer address;
- socklen_t len;
- int family;
-
- len = MIN_SOCKADDR_BUFLEN;
-
- if (getsockname (CAMEL_TCP_STREAM_RAW (stream)->sockfd, saddr, &len) == -1)
+ *len = sizeof(sin);
+ if (getsockname (CAMEL_TCP_STREAM_RAW (stream)->sockfd, saddr, len) == -1)
return NULL;
-
- if (saddr->sa_family == AF_INET) {
- family = CAMEL_TCP_ADDRESS_IPv4;
- address = &sin->sin_addr;
-#ifdef ENABLE_IPv6
- } else if (saddr->sa_family == AF_INET6) {
- family = CAMEL_TCP_ADDRESS_IPv6;
- address = &sin6->sin6_addr;
-#endif
- } else
- return NULL;
-
- return camel_tcp_address_new (family, sin->sin_port, len, address);
+
+ saddr = g_malloc(*len);
+ memcpy(saddr, &sin, *len);
+
+ return saddr;
}
-static CamelTcpAddress *
-stream_get_remote_address (CamelTcpStream *stream)
+static struct sockaddr *
+stream_get_remote_address (CamelTcpStream *stream, socklen_t *len)
{
- unsigned char buf[MIN_SOCKADDR_BUFLEN];
#ifdef ENABLE_IPv6
- struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) buf;
-#endif
- struct sockaddr_in *sin = (struct sockaddr_in *) buf;
- struct sockaddr *saddr = (struct sockaddr *) buf;
- gpointer address;
- socklen_t len;
- int family;
-
- len = MIN_SOCKADDR_BUFLEN;
-
- if (getpeername (CAMEL_TCP_STREAM_RAW (stream)->sockfd, saddr, &len) == -1)
- return NULL;
-
- if (saddr->sa_family == AF_INET) {
- family = CAMEL_TCP_ADDRESS_IPv4;
- address = &sin->sin_addr;
-#ifdef ENABLE_IPv6
- } else if (saddr->sa_family == AF_INET6) {
- family = CAMEL_TCP_ADDRESS_IPv6;
- address = &sin6->sin6_addr;
+ struct sockaddr_in6 sin;
+#else
+ struct sockaddr_in sin;
#endif
- } else
+ struct sockaddr *saddr = (struct sockaddr *)&sin;
+
+ *len = sizeof(sin);
+ if (getpeername (CAMEL_TCP_STREAM_RAW (stream)->sockfd, saddr, len) == -1)
return NULL;
-
- return camel_tcp_address_new (family, sin->sin_port, len, address);
+
+ saddr = g_malloc(*len);
+ memcpy(saddr, &sin, *len);
+
+ return saddr;
}
diff --git a/camel/camel-tcp-stream-ssl.c b/camel/camel-tcp-stream-ssl.c
index 791083186..39bf89d46 100644
--- a/camel/camel-tcp-stream-ssl.c
+++ b/camel/camel-tcp-stream-ssl.c
@@ -80,11 +80,11 @@ static int stream_close (CamelStream *stream);
static PRFileDesc *enable_ssl (CamelTcpStreamSSL *ssl, PRFileDesc *fd);
-static int stream_connect (CamelTcpStream *stream, struct hostent *host, int port);
+static int stream_connect (CamelTcpStream *stream, struct addrinfo *host);
static int stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data);
static int stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data);
-static CamelTcpAddress *stream_get_local_address (CamelTcpStream *stream);
-static CamelTcpAddress *stream_get_remote_address (CamelTcpStream *stream);
+static struct sockaddr *stream_get_local_address (CamelTcpStream *stream, socklen_t *len);
+static struct sockaddr *stream_get_remote_address (CamelTcpStream *stream, socklen_t *len);
struct _CamelTcpStreamSSLPrivate {
PRFileDesc *sockfd;
@@ -1024,30 +1024,58 @@ enable_ssl (CamelTcpStreamSSL *ssl, PRFileDesc *fd)
}
static int
-stream_connect (CamelTcpStream *stream, struct hostent *host, int port)
+sockaddr_to_praddr(struct sockaddr *s, int len, PRNetAddr *addr)
+{
+ /* We assume the ip addresses are the same size - they have to be anyway.
+ We could probably just use memcpy *shrug* */
+
+ memset(addr, 0, sizeof(*addr));
+
+ if (s->sa_family == AF_INET) {
+ struct sockaddr_in *sin = (struct sockaddr_in *)s;
+
+ if (len < sizeof(*sin))
+ return -1;
+
+ addr->inet.family = PR_AF_INET;
+ addr->inet.port = sin->sin_port;
+ memcpy(&addr->inet.ip, &sin->sin_addr, sizeof(addr->inet.ip));
+
+ return 0;
+ }
+#ifdef ENABLE_IPv6
+ else if (s->sa_family == PR_AF_INET6) {
+ struct sockaddr_in6 *sin = (struct sockaddr_in6 *)s;
+
+ if (len < sizeof(*sin))
+ return -1;
+
+ addr->ipv6.family = PR_AF_INET6;
+ addr->ipv6.port = sin->sin6_port;
+ addr->ipv6.flowinfo = sin->sin6_flowinfo;
+ memcpy(&addr->ipv6.ip, &sin->sin6_addr, sizeof(addr->ipv6.ip));
+ addr->ipv6.scope_id = sin->sin6_scope_id;
+
+ return 0;
+ }
+#endif
+
+ return -1;
+}
+
+static int
+socket_connect(CamelTcpStream *stream, struct addrinfo *host)
{
CamelTcpStreamSSL *ssl = CAMEL_TCP_STREAM_SSL (stream);
PRNetAddr netaddr;
PRFileDesc *fd, *cancel_fd;
-
- g_return_val_if_fail (host != NULL, -1);
-
- memset ((void *) &netaddr, 0, sizeof (PRNetAddr));
-#ifdef ENABLE_IPv6
- if (host->h_addrtype == AF_INET6)
- memcpy (&netaddr.ipv6.ip, host->h_addr, sizeof (netaddr.ipv6.ip));
- else
- memcpy (&netaddr.inet.ip, host->h_addr, sizeof (netaddr.inet.ip));
-#else
- memcpy (&netaddr.inet.ip, host->h_addr, sizeof (netaddr.inet.ip));
-#endif
-
- if (PR_InitializeNetAddr (PR_IpAddrNull, port, &netaddr) == PR_FAILURE) {
- set_errno (PR_GetError ());
+
+ if (sockaddr_to_praddr(host->ai_addr, host->ai_addrlen, &netaddr) != 0) {
+ errno = EINVAL;
return -1;
}
- fd = PR_OpenTCPSocket (host->h_addrtype);
+ fd = PR_OpenTCPSocket(netaddr.raw.family);
if (fd == NULL) {
set_errno (PR_GetError ());
return -1;
@@ -1126,6 +1154,17 @@ stream_connect (CamelTcpStream *stream, struct hostent *host, int port)
return 0;
}
+static int
+stream_connect(CamelTcpStream *stream, struct addrinfo *host)
+{
+ while (host) {
+ if (socket_connect(stream, host) == 0)
+ return 0;
+ host = host->ai_next;
+ }
+
+ return -1;
+}
static int
stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data)
@@ -1157,56 +1196,61 @@ stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data)
return 0;
}
-static CamelTcpAddress *
-stream_get_local_address (CamelTcpStream *stream)
+static struct sockaddr *
+sockaddr_from_praddr(PRNetAddr *addr, socklen_t *len)
+{
+ /* We assume the ip addresses are the same size - they have to be anyway */
+
+ if (addr->raw.family == PR_AF_INET) {
+ struct sockaddr_in *sin = g_malloc0(sizeof(*sin));
+
+ sin->sin_family = AF_INET;
+ sin->sin_port = addr->inet.port;
+ memcpy(&sin->sin_addr, &addr->inet.ip, sizeof(sin->sin_addr));
+ *len = sizeof(*sin);
+
+ return (struct sockaddr *)sin;
+ }
+#ifdef ENABLE_IPv6
+ else if (addr->raw.family == PR_AF_INET6) {
+ struct sockaddr_in6 *sin = g_malloc0(sizeof(*sin));
+
+ sin->sin6_family = AF_INET6;
+ sin->sin6_port = addr->ipv6.port;
+ sin->sin6_flowinfo = addr->ipv6.flowinfo;
+ memcpy(&sin->sin6_addr, &addr->ipv6.ip, sizeof(sin->sin6_addr));
+ sin->sin6_scope_id = addr->ipv6.scope_id;
+ *len = sizeof(*sin);
+
+ return (struct sockaddr *)sin;
+ }
+#endif
+
+ return NULL;
+}
+
+static struct sockaddr *
+stream_get_local_address(CamelTcpStream *stream, socklen_t *len)
{
PRFileDesc *sockfd = CAMEL_TCP_STREAM_SSL (stream)->priv->sockfd;
- int family, length;
- gpointer address;
PRNetAddr addr;
- PR_GetSockName (sockfd, &addr);
-
- if (addr.inet.family == PR_AF_INET) {
- family = CAMEL_TCP_ADDRESS_IPv4;
- address = &addr.inet.ip;
- length = 4;
-#ifdef ENABLE_IPv6
- } else if (addr.inet.family == PR_AF_INET6) {
- family = CAMEL_TCP_ADDRESS_IPv6;
- address = &addr.ipv6.ip;
- length = 16;
-#endif
- } else
+ if (PR_GetSockName(sockfd, &addr) != PR_SUCCESS)
return NULL;
-
- return camel_tcp_address_new (family, addr.inet.port, length, address);
+
+ return sockaddr_from_praddr(&addr, len);
}
-static CamelTcpAddress *
-stream_get_remote_address (CamelTcpStream *stream)
+static struct sockaddr *
+stream_get_remote_address (CamelTcpStream *stream, socklen_t *len)
{
PRFileDesc *sockfd = CAMEL_TCP_STREAM_SSL (stream)->priv->sockfd;
- int family, length;
- gpointer address;
PRNetAddr addr;
- PR_GetPeerName (sockfd, &addr);
-
- if (addr.inet.family == PR_AF_INET) {
- family = CAMEL_TCP_ADDRESS_IPv4;
- address = &addr.inet.ip;
- length = sizeof (addr.inet.ip);
-#ifdef ENABLE_IPv6
- } else if (addr.inet.family == PR_AF_INET6) {
- family = CAMEL_TCP_ADDRESS_IPv6;
- address = &addr.ipv6.ip;
- length = sizeof (addr.ipv6.ip);
-#endif
- } else
+ if (PR_GetPeerName(sockfd, &addr) != PR_SUCCESS)
return NULL;
-
- return camel_tcp_address_new (family, addr.inet.port, length, address);
+
+ return sockaddr_from_praddr(&addr, len);
}
#endif /* HAVE_NSS */
diff --git a/camel/camel-tcp-stream.c b/camel/camel-tcp-stream.c
index fbbcbec45..3aa2d0d36 100644
--- a/camel/camel-tcp-stream.c
+++ b/camel/camel-tcp-stream.c
@@ -35,12 +35,11 @@ static CamelStreamClass *parent_class = NULL;
/* Returns the class for a CamelTcpStream */
#define CTS_CLASS(so) CAMEL_TCP_STREAM_CLASS (CAMEL_OBJECT_GET_CLASS(so))
-static int tcp_connect (CamelTcpStream *stream, struct hostent *host, int port);
+static int tcp_connect (CamelTcpStream *stream, struct addrinfo *host);
static int tcp_getsockopt (CamelTcpStream *stream, CamelSockOptData *data);
static int tcp_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data);
-static CamelTcpAddress *tcp_get_local_address (CamelTcpStream *stream);
-static CamelTcpAddress *tcp_get_remote_address (CamelTcpStream *stream);
-
+static struct sockaddr *tcp_get_local_address (CamelTcpStream *stream, socklen_t *len);
+static struct sockaddr *tcp_get_remote_address (CamelTcpStream *stream, socklen_t *len);
static void
camel_tcp_stream_class_init (CamelTcpStreamClass *camel_tcp_stream_class)
@@ -84,7 +83,7 @@ camel_tcp_stream_get_type (void)
static int
-tcp_connect (CamelTcpStream *stream, struct hostent *host, int port)
+tcp_connect (CamelTcpStream *stream, struct addrinfo *host)
{
w(g_warning ("CamelTcpStream::connect called on default implementation"));
return -1;
@@ -93,22 +92,21 @@ tcp_connect (CamelTcpStream *stream, struct hostent *host, int port)
/**
* camel_tcp_stream_connect:
* @stream: a CamelTcpStream object.
- * @host: a hostent value
- * @port: port
+ * @host: A linked list of addrinfo structures to try to connect, in
+ * the order of most likely to least likely to work.
*
* Create a socket and connect based upon the data provided.
*
* Return value: zero on success or -1 on fail.
**/
int
-camel_tcp_stream_connect (CamelTcpStream *stream, struct hostent *host, int port)
+camel_tcp_stream_connect (CamelTcpStream *stream, struct addrinfo *host)
{
g_return_val_if_fail (CAMEL_IS_TCP_STREAM (stream), -1);
- return CTS_CLASS (stream)->connect (stream, host, port);
+ return CTS_CLASS (stream)->connect (stream, host);
}
-
static int
tcp_getsockopt (CamelTcpStream *stream, CamelSockOptData *data)
{
@@ -116,7 +114,6 @@ tcp_getsockopt (CamelTcpStream *stream, CamelSockOptData *data)
return -1;
}
-
/**
* camel_tcp_stream_getsockopt:
* @stream: tcp stream object
@@ -134,7 +131,6 @@ camel_tcp_stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data)
return CTS_CLASS (stream)->getsockopt (stream, data);
}
-
static int
tcp_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data)
{
@@ -142,7 +138,6 @@ tcp_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data)
return -1;
}
-
/**
* camel_tcp_stream_setsockopt:
* @stream: tcp stream object
@@ -160,9 +155,8 @@ camel_tcp_stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *dat
return CTS_CLASS (stream)->setsockopt (stream, data);
}
-
-static CamelTcpAddress *
-tcp_get_local_address (CamelTcpStream *stream)
+static struct sockaddr *
+tcp_get_local_address (CamelTcpStream *stream, socklen_t *len)
{
w(g_warning ("CamelTcpStream::get_local_address called on default implementation"));
return NULL;
@@ -171,23 +165,24 @@ tcp_get_local_address (CamelTcpStream *stream)
/**
* camel_tcp_stream_get_local_address:
* @stream: tcp stream object
+ * @len: Pointer to address length which must be supplied.
*
* Get the local address of @stream.
*
* Return value: the stream's local address (which must be freed with
- * camel_tcp_address_free()) if the stream is connected, or %NULL if not.
+ * g_free()) if the stream is connected, or %NULL if not.
**/
-CamelTcpAddress *
-camel_tcp_stream_get_local_address (CamelTcpStream *stream)
+struct sockaddr *
+camel_tcp_stream_get_local_address (CamelTcpStream *stream, socklen_t *len)
{
g_return_val_if_fail (CAMEL_IS_TCP_STREAM (stream), NULL);
+ g_return_val_if_fail(len != NULL, NULL);
- return CTS_CLASS (stream)->get_local_address (stream);
+ return CTS_CLASS (stream)->get_local_address (stream, len);
}
-
-static CamelTcpAddress *
-tcp_get_remote_address (CamelTcpStream *stream)
+static struct sockaddr *
+tcp_get_remote_address (CamelTcpStream *stream, socklen_t *len)
{
w(g_warning ("CamelTcpStream::get_remote_address called on default implementation"));
return NULL;
@@ -196,54 +191,18 @@ tcp_get_remote_address (CamelTcpStream *stream)
/**
* camel_tcp_stream_get_remote_address:
* @stream: tcp stream object
+ * @len: Pointer to address length, which must be supplied.
*
* Get the remote address of @stream.
*
* Return value: the stream's remote address (which must be freed with
- * camel_tcp_address_free()) if the stream is connected, or %NULL if not.
+ * g_free()) if the stream is connected, or %NULL if not.
**/
-CamelTcpAddress *
-camel_tcp_stream_get_remote_address (CamelTcpStream *stream)
+struct sockaddr *
+camel_tcp_stream_get_remote_address (CamelTcpStream *stream, socklen_t *len)
{
g_return_val_if_fail (CAMEL_IS_TCP_STREAM (stream), NULL);
-
- return CTS_CLASS (stream)->get_remote_address (stream);
-}
+ g_return_val_if_fail(len != NULL, NULL);
-
-/**
- * camel_tcp_address_new:
- * @family: the address family
- * @port: the port number (in network byte order)
- * @length: the length of @address
- * @address: the address data (family dependent, in network byte order)
- *
- * Return value: a new CamelTcpAddress.
- **/
-CamelTcpAddress *
-camel_tcp_address_new (CamelTcpAddressFamily family, gushort port,
- gushort length, gpointer address)
-{
- CamelTcpAddress *addr;
-
- addr = g_malloc (sizeof (CamelTcpAddress) + length - 1);
- addr->family = family;
- addr->port = port;
- addr->length = length;
- memcpy (&addr->address, address, length);
-
- return addr;
-}
-
-
-/**
- * camel_tcp_address_free:
- * @address: the address
- *
- * Frees @address.
- **/
-void
-camel_tcp_address_free (CamelTcpAddress *address)
-{
- g_free (address);
+ return CTS_CLASS (stream)->get_remote_address (stream, len);
}
diff --git a/camel/camel-tcp-stream.h b/camel/camel-tcp-stream.h
index 9472da10e..b7be61dbe 100644
--- a/camel/camel-tcp-stream.h
+++ b/camel/camel-tcp-stream.h
@@ -88,18 +88,6 @@ typedef struct _CamelSockOptData {
} value;
} CamelSockOptData;
-typedef enum {
- CAMEL_TCP_ADDRESS_IPv4,
- CAMEL_TCP_ADDRESS_IPv6
-} CamelTcpAddressFamily;
-
-typedef struct {
- CamelTcpAddressFamily family;
- gushort port, length;
- guint8 address[1];
-} CamelTcpAddress;
-
-
struct _CamelTcpStream {
CamelStream parent_object;
@@ -109,29 +97,24 @@ typedef struct {
CamelStreamClass parent_class;
/* Virtual methods */
- int (*connect) (CamelTcpStream *stream, struct hostent *host, int port);
+ int (*connect) (CamelTcpStream *stream, struct addrinfo *host);
int (*getsockopt) (CamelTcpStream *stream, CamelSockOptData *data);
int (*setsockopt) (CamelTcpStream *stream, const CamelSockOptData *data);
- CamelTcpAddress * (*get_local_address) (CamelTcpStream *stream);
- CamelTcpAddress * (*get_remote_address) (CamelTcpStream *stream);
+ struct sockaddr * (*get_local_address) (CamelTcpStream *stream, socklen_t *len);
+ struct sockaddr * (*get_remote_address) (CamelTcpStream *stream, socklen_t *len);
} CamelTcpStreamClass;
/* Standard Camel function */
CamelType camel_tcp_stream_get_type (void);
/* public methods */
-int camel_tcp_stream_connect (CamelTcpStream *stream, struct hostent *host, int port);
+int camel_tcp_stream_connect (CamelTcpStream *stream, struct addrinfo *host);
int camel_tcp_stream_getsockopt (CamelTcpStream *stream, CamelSockOptData *data);
int camel_tcp_stream_setsockopt (CamelTcpStream *stream, const CamelSockOptData *data);
-CamelTcpAddress *camel_tcp_stream_get_local_address (CamelTcpStream *stream);
-CamelTcpAddress *camel_tcp_stream_get_remote_address (CamelTcpStream *stream);
-
-CamelTcpAddress *camel_tcp_address_new (CamelTcpAddressFamily family,
- gushort port, gushort length,
- gpointer address);
-void camel_tcp_address_free (CamelTcpAddress *address);
+struct sockaddr *camel_tcp_stream_get_local_address (CamelTcpStream *stream, socklen_t *len);
+struct sockaddr *camel_tcp_stream_get_remote_address (CamelTcpStream *stream, socklen_t *len);
#ifdef __cplusplus
}
diff --git a/camel/providers/imap/camel-imap-store.c b/camel/providers/imap/camel-imap-store.c
new file mode 100644
index 000000000..c0c0bcc8a
--- /dev/null
+++ b/camel/providers/imap/camel-imap-store.c
@@ -0,0 +1,3258 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* camel-imap-store.c : class for an imap store */
+
+/*
+ * Authors:
+ * Dan Winship <danw@ximian.com>
+ * Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2000, 2003 Ximian, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "e-util/e-path.h"
+
+#include "camel-imap-store.h"
+#include "camel-imap-store-summary.h"
+#include "camel-imap-folder.h"
+#include "camel-imap-utils.h"
+#include "camel-imap-command.h"
+#include "camel-imap-summary.h"
+#include "camel-imap-message-cache.h"
+#include "camel-disco-diary.h"
+#include "camel-file-utils.h"
+#include "camel-folder.h"
+#include "camel-exception.h"
+#include "camel-session.h"
+#include "camel-stream.h"
+#include "camel-stream-buffer.h"
+#include "camel-stream-fs.h"
+#include "camel-stream-process.h"
+#include "camel-tcp-stream-raw.h"
+#include "camel-tcp-stream-ssl.h"
+#include "camel-url.h"
+#include "camel-sasl.h"
+#include "camel-utf8.h"
+#include "camel-string-utils.h"
+
+#include "camel-imap-private.h"
+#include "camel-private.h"
+
+#include "camel-debug.h"
+
+#define d(x)
+
+/* Specified in RFC 2060 */
+#define IMAP_PORT 143
+#define SIMAP_PORT 993
+
+static CamelDiscoStoreClass *parent_class = NULL;
+
+static char imap_tag_prefix = 'A';
+
+static void construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex);
+
+static int imap_setv (CamelObject *object, CamelException *ex, CamelArgV *args);
+static int imap_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args);
+
+static char *imap_get_name (CamelService *service, gboolean brief);
+
+static gboolean can_work_offline (CamelDiscoStore *disco_store);
+static gboolean imap_connect_online (CamelService *service, CamelException *ex);
+static gboolean imap_connect_offline (CamelService *service, CamelException *ex);
+static gboolean imap_disconnect_online (CamelService *service, gboolean clean, CamelException *ex);
+static gboolean imap_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex);
+static void imap_noop (CamelStore *store, CamelException *ex);
+static CamelFolder *imap_get_junk(CamelStore *store, CamelException *ex);
+static CamelFolder *imap_get_trash(CamelStore *store, CamelException *ex);
+static GList *query_auth_types (CamelService *service, CamelException *ex);
+static guint hash_folder_name (gconstpointer key);
+static gint compare_folder_name (gconstpointer a, gconstpointer b);
+static CamelFolder *get_folder_online (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);
+static CamelFolder *get_folder_offline (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);
+
+static CamelFolderInfo *create_folder (CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex);
+static void delete_folder (CamelStore *store, const char *folder_name, CamelException *ex);
+static void rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);
+static CamelFolderInfo *get_folder_info_online (CamelStore *store,
+ const char *top,
+ guint32 flags,
+ CamelException *ex);
+static CamelFolderInfo *get_folder_info_offline (CamelStore *store,
+ const char *top,
+ guint32 flags,
+ CamelException *ex);
+static gboolean folder_subscribed (CamelStore *store, const char *folder_name);
+static void subscribe_folder (CamelStore *store, const char *folder_name,
+ CamelException *ex);
+static void unsubscribe_folder (CamelStore *store, const char *folder_name,
+ CamelException *ex);
+
+static void get_folders_online (CamelImapStore *imap_store, const char *pattern,
+ GPtrArray *folders, gboolean lsub, CamelException *ex);
+
+
+static void imap_folder_effectively_unsubscribed(CamelImapStore *imap_store, const char *folder_name, CamelException *ex);
+static gboolean imap_check_folder_still_extant (CamelImapStore *imap_store, const char *full_name, CamelException *ex);
+static void imap_forget_folder(CamelImapStore *imap_store, const char *folder_name, CamelException *ex);
+static void imap_set_server_level (CamelImapStore *store);
+
+static void
+camel_imap_store_class_init (CamelImapStoreClass *camel_imap_store_class)
+{
+ CamelObjectClass *camel_object_class =
+ CAMEL_OBJECT_CLASS (camel_imap_store_class);
+ CamelServiceClass *camel_service_class =
+ CAMEL_SERVICE_CLASS (camel_imap_store_class);
+ CamelStoreClass *camel_store_class =
+ CAMEL_STORE_CLASS (camel_imap_store_class);
+ CamelDiscoStoreClass *camel_disco_store_class =
+ CAMEL_DISCO_STORE_CLASS (camel_imap_store_class);
+
+ parent_class = CAMEL_DISCO_STORE_CLASS (camel_type_get_global_classfuncs (camel_disco_store_get_type ()));
+
+ /* virtual method overload */
+ camel_object_class->setv = imap_setv;
+ camel_object_class->getv = imap_getv;
+
+ camel_service_class->construct = construct;
+ camel_service_class->query_auth_types = query_auth_types;
+ camel_service_class->get_name = imap_get_name;
+
+ camel_store_class->hash_folder_name = hash_folder_name;
+ camel_store_class->compare_folder_name = compare_folder_name;
+ camel_store_class->create_folder = create_folder;
+ camel_store_class->delete_folder = delete_folder;
+ camel_store_class->rename_folder = rename_folder;
+ camel_store_class->free_folder_info = camel_store_free_folder_info_full;
+ camel_store_class->folder_subscribed = folder_subscribed;
+ camel_store_class->subscribe_folder = subscribe_folder;
+ camel_store_class->unsubscribe_folder = unsubscribe_folder;
+ camel_store_class->noop = imap_noop;
+ camel_store_class->get_trash = imap_get_trash;
+ camel_store_class->get_junk = imap_get_junk;
+
+ camel_disco_store_class->can_work_offline = can_work_offline;
+ camel_disco_store_class->connect_online = imap_connect_online;
+ camel_disco_store_class->connect_offline = imap_connect_offline;
+ camel_disco_store_class->disconnect_online = imap_disconnect_online;
+ camel_disco_store_class->disconnect_offline = imap_disconnect_offline;
+ camel_disco_store_class->get_folder_online = get_folder_online;
+ camel_disco_store_class->get_folder_offline = get_folder_offline;
+ camel_disco_store_class->get_folder_resyncing = get_folder_online;
+ camel_disco_store_class->get_folder_info_online = get_folder_info_online;
+ camel_disco_store_class->get_folder_info_offline = get_folder_info_offline;
+ camel_disco_store_class->get_folder_info_resyncing = get_folder_info_online;
+}
+
+static gboolean
+free_key (gpointer key, gpointer value, gpointer user_data)
+{
+ g_free (key);
+ return TRUE;
+}
+
+static void
+camel_imap_store_finalize (CamelObject *object)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
+
+ /* This frees current_folder, folders, authtypes, streams, and namespace. */
+ camel_service_disconnect((CamelService *)imap_store, TRUE, NULL);
+
+ if (imap_store->summary) {
+ camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
+ camel_object_unref(imap_store->summary);
+ }
+
+ if (imap_store->base_url)
+ g_free (imap_store->base_url);
+ if (imap_store->storage_path)
+ g_free (imap_store->storage_path);
+}
+
+static void
+camel_imap_store_init (gpointer object, gpointer klass)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
+
+ imap_store->istream = NULL;
+ imap_store->ostream = NULL;
+
+ imap_store->dir_sep = '\0';
+ imap_store->current_folder = NULL;
+ imap_store->connected = FALSE;
+ imap_store->preauthed = FALSE;
+
+ imap_store->tag_prefix = imap_tag_prefix++;
+ if (imap_tag_prefix > 'Z')
+ imap_tag_prefix = 'A';
+}
+
+CamelType
+camel_imap_store_get_type (void)
+{
+ static CamelType camel_imap_store_type = CAMEL_INVALID_TYPE;
+
+ if (camel_imap_store_type == CAMEL_INVALID_TYPE) {
+ camel_imap_store_type =
+ camel_type_register (CAMEL_DISCO_STORE_TYPE,
+ "CamelImapStore",
+ sizeof (CamelImapStore),
+ sizeof (CamelImapStoreClass),
+ (CamelObjectClassInitFunc) camel_imap_store_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_imap_store_init,
+ (CamelObjectFinalizeFunc) camel_imap_store_finalize);
+ }
+
+ return camel_imap_store_type;
+}
+
+static void
+construct (CamelService *service, CamelSession *session,
+ CamelProvider *provider, CamelURL *url,
+ CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (service);
+ CamelStore *store = CAMEL_STORE (service);
+ char *tmp;
+ CamelURL *summary_url;
+
+ CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
+ if (camel_exception_is_set (ex))
+ return;
+
+ imap_store->storage_path = camel_session_get_storage_path (session, service, ex);
+ if (!imap_store->storage_path)
+ return;
+
+ /* FIXME */
+ imap_store->base_url = camel_url_to_string (service->url, (CAMEL_URL_HIDE_PASSWORD |
+ CAMEL_URL_HIDE_PARAMS |
+ CAMEL_URL_HIDE_AUTH));
+
+ imap_store->parameters = 0;
+ if (camel_url_get_param (url, "use_lsub"))
+ store->flags |= CAMEL_STORE_SUBSCRIPTIONS;
+ if (camel_url_get_param (url, "namespace")) {
+ imap_store->parameters |= IMAP_PARAM_OVERRIDE_NAMESPACE;
+ g_free(imap_store->namespace);
+ imap_store->namespace = g_strdup (camel_url_get_param (url, "namespace"));
+ }
+ if (camel_url_get_param (url, "check_all"))
+ imap_store->parameters |= IMAP_PARAM_CHECK_ALL;
+ if (camel_url_get_param (url, "filter")) {
+ imap_store->parameters |= IMAP_PARAM_FILTER_INBOX;
+ store->flags |= CAMEL_STORE_FILTER_INBOX;
+ }
+ if (camel_url_get_param (url, "filter_junk"))
+ imap_store->parameters |= IMAP_PARAM_FILTER_JUNK;
+ if (camel_url_get_param (url, "filter_junk_inbox"))
+ imap_store->parameters |= IMAP_PARAM_FILTER_JUNK_INBOX;
+
+ /* setup/load the store summary */
+ tmp = alloca(strlen(imap_store->storage_path)+32);
+ sprintf(tmp, "%s/.ev-store-summary", imap_store->storage_path);
+ imap_store->summary = camel_imap_store_summary_new();
+ camel_store_summary_set_filename((CamelStoreSummary *)imap_store->summary, tmp);
+ summary_url = camel_url_new(imap_store->base_url, NULL);
+ camel_store_summary_set_uri_base((CamelStoreSummary *)imap_store->summary, summary_url);
+ camel_url_free(summary_url);
+ if (camel_store_summary_load((CamelStoreSummary *)imap_store->summary) == 0) {
+ CamelImapStoreSummary *is = imap_store->summary;
+
+ if (is->namespace) {
+ /* if namespace has changed, clear folder list */
+ if (imap_store->namespace && strcmp(imap_store->namespace, is->namespace->full_name) != 0) {
+ camel_store_summary_clear((CamelStoreSummary *)is);
+ } else {
+ imap_store->namespace = g_strdup(is->namespace->full_name);
+ imap_store->dir_sep = is->namespace->sep;
+ }
+ }
+
+ imap_store->capabilities = is->capabilities;
+ imap_set_server_level(imap_store);
+ }
+}
+
+static int
+imap_setv (CamelObject *object, CamelException *ex, CamelArgV *args)
+{
+ CamelImapStore *store = (CamelImapStore *) object;
+ guint32 tag, flags;
+ int i;
+
+ for (i = 0; i < args->argc; i++) {
+ tag = args->argv[i].tag;
+
+ /* make sure this is an arg we're supposed to handle */
+ if ((tag & CAMEL_ARG_TAG) <= CAMEL_IMAP_STORE_ARG_FIRST ||
+ (tag & CAMEL_ARG_TAG) >= CAMEL_IMAP_STORE_ARG_FIRST + 100)
+ continue;
+
+ switch (tag) {
+ case CAMEL_IMAP_STORE_NAMESPACE:
+ if (strcmp (store->namespace, args->argv[i].ca_str) != 0) {
+ g_free (store->namespace);
+ store->namespace = g_strdup (args->argv[i].ca_str);
+ /* the current imap code will need to do a reconnect for this to take effect */
+ /*reconnect = TRUE;*/
+ }
+ break;
+ case CAMEL_IMAP_STORE_OVERRIDE_NAMESPACE:
+ flags = args->argv[i].ca_int ? IMAP_PARAM_OVERRIDE_NAMESPACE : 0;
+ flags |= (store->parameters & ~IMAP_PARAM_OVERRIDE_NAMESPACE);
+
+ if (store->parameters != flags) {
+ store->parameters = flags;
+ /* the current imap code will need to do a reconnect for this to take effect */
+ /*reconnect = TRUE;*/
+ }
+ break;
+ case CAMEL_IMAP_STORE_CHECK_ALL:
+ flags = args->argv[i].ca_int ? IMAP_PARAM_CHECK_ALL : 0;
+ flags |= (store->parameters & ~IMAP_PARAM_CHECK_ALL);
+ store->parameters = flags;
+ /* no need to reconnect for this option to take effect... */
+ break;
+ case CAMEL_IMAP_STORE_FILTER_INBOX:
+ flags = args->argv[i].ca_int ? IMAP_PARAM_FILTER_INBOX : 0;
+ flags |= (store->parameters & ~IMAP_PARAM_FILTER_INBOX);
+ store->parameters = flags;
+ /* no need to reconnect for this option to take effect... */
+ break;
+ case CAMEL_IMAP_STORE_FILTER_JUNK:
+ flags = args->argv[i].ca_int ? IMAP_PARAM_FILTER_JUNK : 0;
+ store->parameters = flags | (store->parameters & ~IMAP_PARAM_FILTER_JUNK);
+ break;
+ case CAMEL_IMAP_STORE_FILTER_JUNK_INBOX:
+ flags = args->argv[i].ca_int ? IMAP_PARAM_FILTER_JUNK_INBOX : 0;
+ store->parameters = flags | (store->parameters & ~IMAP_PARAM_FILTER_JUNK_INBOX);
+ break;
+ default:
+ /* error?? */
+ continue;
+ }
+
+ /* let our parent know that we've handled this arg */
+ camel_argv_ignore (args, i);
+ }
+
+ /* FIXME: if we need to reconnect for a change to take affect,
+ we need to do it here... or, better yet, somehow chain it
+ up to CamelService's setv implementation. */
+
+ return CAMEL_OBJECT_CLASS (parent_class)->setv (object, ex, args);
+}
+
+static int
+imap_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args)
+{
+ CamelImapStore *store = (CamelImapStore *) object;
+ guint32 tag;
+ int i;
+
+ for (i = 0; i < args->argc; i++) {
+ tag = args->argv[i].tag;
+
+ /* make sure this is an arg we're supposed to handle */
+ if ((tag & CAMEL_ARG_TAG) <= CAMEL_IMAP_STORE_ARG_FIRST ||
+ (tag & CAMEL_ARG_TAG) >= CAMEL_IMAP_STORE_ARG_FIRST + 100)
+ continue;
+
+ switch (tag) {
+ case CAMEL_IMAP_STORE_NAMESPACE:
+ *args->argv[i].ca_str = store->namespace;
+ break;
+ case CAMEL_IMAP_STORE_OVERRIDE_NAMESPACE:
+ *args->argv[i].ca_int = store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE ? TRUE : FALSE;
+ break;
+ case CAMEL_IMAP_STORE_CHECK_ALL:
+ *args->argv[i].ca_int = store->parameters & IMAP_PARAM_CHECK_ALL ? TRUE : FALSE;
+ break;
+ case CAMEL_IMAP_STORE_FILTER_INBOX:
+ *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_INBOX ? TRUE : FALSE;
+ break;
+ case CAMEL_IMAP_STORE_FILTER_JUNK:
+ *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_JUNK ? TRUE : FALSE;
+ break;
+ case CAMEL_IMAP_STORE_FILTER_JUNK_INBOX:
+ *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_JUNK_INBOX ? TRUE : FALSE;
+ break;
+ default:
+ /* error? */
+ break;
+ }
+ }
+
+ return CAMEL_OBJECT_CLASS (parent_class)->getv (object, ex, args);
+}
+
+static char *
+imap_get_name (CamelService *service, gboolean brief)
+{
+ if (brief)
+ return g_strdup_printf (_("IMAP server %s"), service->url->host);
+ else
+ return g_strdup_printf (_("IMAP service for %s on %s"),
+ service->url->user, service->url->host);
+}
+
+static void
+imap_set_server_level (CamelImapStore *store)
+{
+ if (store->capabilities & IMAP_CAPABILITY_IMAP4REV1) {
+ store->server_level = IMAP_LEVEL_IMAP4REV1;
+ store->capabilities |= IMAP_CAPABILITY_STATUS;
+ } else if (store->capabilities & IMAP_CAPABILITY_IMAP4)
+ store->server_level = IMAP_LEVEL_IMAP4;
+ else
+ store->server_level = IMAP_LEVEL_UNKNOWN;
+}
+
+static struct {
+ const char *name;
+ guint32 flag;
+} capabilities[] = {
+ { "IMAP4", IMAP_CAPABILITY_IMAP4 },
+ { "IMAP4REV1", IMAP_CAPABILITY_IMAP4REV1 },
+ { "STATUS", IMAP_CAPABILITY_STATUS },
+ { "NAMESPACE", IMAP_CAPABILITY_NAMESPACE },
+ { "UIDPLUS", IMAP_CAPABILITY_UIDPLUS },
+ { "LITERAL+", IMAP_CAPABILITY_LITERALPLUS },
+ { "STARTTLS", IMAP_CAPABILITY_STARTTLS },
+ { NULL, 0 }
+};
+
+static gboolean
+imap_get_capability (CamelService *service, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelImapResponse *response;
+ char *result, *capa, *lasts;
+ int i;
+
+ CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
+
+ /* Find out the IMAP capabilities */
+ /* We assume we have utf8 capable search until a failed search tells us otherwise */
+ store->capabilities = IMAP_CAPABILITY_utf8_search;
+ store->authtypes = g_hash_table_new (g_str_hash, g_str_equal);
+ response = camel_imap_command (store, NULL, ex, "CAPABILITY");
+ if (!response)
+ return FALSE;
+ result = camel_imap_response_extract (store, response, "CAPABILITY ", ex);
+ if (!result)
+ return FALSE;
+
+ /* Skip over "* CAPABILITY ". */
+ capa = result + 13;
+ for (capa = strtok_r (capa, " ", &lasts); capa;
+ capa = strtok_r (NULL, " ", &lasts)) {
+ if (!strncmp (capa, "AUTH=", 5)) {
+ g_hash_table_insert (store->authtypes,
+ g_strdup (capa + 5),
+ GINT_TO_POINTER (1));
+ continue;
+ }
+ for (i = 0; capabilities[i].name; i++) {
+ if (strcasecmp (capa, capabilities[i].name) == 0) {
+ store->capabilities |= capabilities[i].flag;
+ break;
+ }
+ }
+ }
+ g_free (result);
+
+ imap_set_server_level (store);
+
+ if (store->summary->capabilities != store->capabilities) {
+ store->summary->capabilities = store->capabilities;
+ camel_store_summary_touch((CamelStoreSummary *)store->summary);
+ camel_store_summary_save((CamelStoreSummary *)store->summary);
+ }
+
+ return TRUE;
+}
+
+enum {
+ USE_SSL_NEVER,
+ USE_SSL_ALWAYS,
+ USE_SSL_WHEN_POSSIBLE
+};
+
+#define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
+#define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
+
+static gboolean
+connect_to_server (CamelService *service, int ssl_mode, int try_starttls, CamelException *ex)
+{
+ CamelImapStore *store = (CamelImapStore *) service;
+ CamelImapResponse *response;
+ CamelStream *tcp_stream;
+ CamelSockOptData sockopt;
+ gboolean force_imap4 = FALSE;
+ int clean_quit;
+ int ret;
+ char *buf;
+ struct addrinfo *ai, hints = { 0 };
+ char *serv;
+
+ /* FIXME: this connect stuff is duplicated everywhere */
+
+ if (service->url->port) {
+ serv = g_alloca(16);
+ sprintf(serv, "%d", service->url->port);
+ } else
+ serv = "imap";
+
+ if (ssl_mode != USE_SSL_NEVER) {
+#ifdef HAVE_SSL
+ if (try_starttls) {
+ tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
+ } else {
+ if (service->url->port == 0)
+ serv = "imaps";
+ tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
+ }
+#else
+ if (!try_starttls && service->url->port == 0)
+ serv = "imaps";
+
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv,
+ _("SSL unavailable"));
+ return FALSE;
+#endif /* HAVE_SSL */
+ } else {
+ tcp_stream = camel_tcp_stream_raw_new ();
+ }
+
+ hints.ai_socktype = SOCK_STREAM;
+ ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
+ if (ai == NULL) {
+ camel_object_unref(tcp_stream);
+ return FALSE;
+ }
+
+ ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai);
+ camel_freeaddrinfo(ai);
+ if (ret == -1) {
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("Connection cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv, g_strerror (errno));
+
+ camel_object_unref (tcp_stream);
+
+ return FALSE;
+ }
+
+ store->ostream = tcp_stream;
+ store->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
+
+ store->connected = TRUE;
+ store->preauthed = FALSE;
+ store->command = 0;
+
+ /* Disable Nagle - we send a lot of small requests which nagle slows down */
+ sockopt.option = CAMEL_SOCKOPT_NODELAY;
+ sockopt.value.no_delay = TRUE;
+ camel_tcp_stream_setsockopt((CamelTcpStream *)tcp_stream, &sockopt);
+
+ /* Set keepalive - needed for some hosts/router configurations, we're idle a lot */
+ sockopt.option = CAMEL_SOCKOPT_KEEPALIVE;
+ sockopt.value.keep_alive = TRUE;
+ camel_tcp_stream_setsockopt((CamelTcpStream *)tcp_stream, &sockopt);
+
+ /* Read the greeting, if any, and deal with PREAUTH */
+ if (camel_imap_store_readline (store, &buf, ex) < 0) {
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ store->connected = FALSE;
+
+ return FALSE;
+ }
+
+ if (!strncmp(buf, "* PREAUTH", 9))
+ store->preauthed = TRUE;
+
+ if (strstr (buf, "Courier-IMAP")) {
+ /* Courier-IMAP is braindamaged. So far this flag only
+ * works around the fact that Courier-IMAP is known to
+ * give invalid BODY responses seemingly because its
+ * MIME parser sucks. In any event, we can't rely on
+ * them so we always have to request the full messages
+ * rather than getting individual parts. */
+ store->braindamaged = TRUE;
+ } else if (strstr (buf, "WEB.DE") || strstr (buf, "Mail2World")) {
+ /* This is a workaround for servers which advertise
+ * IMAP4rev1 but which can sometimes subtly break in
+ * various ways if we try to use IMAP4rev1 queries.
+ *
+ * WEB.DE: when querying for HEADER.FIELDS.NOT, it
+ * returns an empty literal for the headers. Many
+ * complaints about empty message-list fields on the
+ * mailing lists and probably a few bugzilla bugs as
+ * well.
+ *
+ * Mail2World (aka NamePlanet): When requesting
+ * message info's, it ignores the fact that we
+ * requested BODY.PEEK[HEADER.FIELDS.NOT (RECEIVED)]
+ * and so the responses are incomplete. See bug #58766
+ * for details.
+ **/
+ force_imap4 = TRUE;
+ }
+
+ g_free (buf);
+
+ /* get the imap server capabilities */
+ if (!imap_get_capability (service, ex)) {
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ store->connected = FALSE;
+ return FALSE;
+ }
+
+ if (force_imap4) {
+ store->capabilities &= ~IMAP_CAPABILITY_IMAP4REV1;
+ store->server_level = IMAP_LEVEL_IMAP4;
+ }
+
+#ifdef HAVE_SSL
+ if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
+ if (store->capabilities & IMAP_CAPABILITY_STARTTLS)
+ goto starttls;
+ } else if (ssl_mode == USE_SSL_ALWAYS) {
+ if (try_starttls) {
+ if (store->capabilities & IMAP_CAPABILITY_STARTTLS) {
+ /* attempt to toggle STARTTLS mode */
+ goto starttls;
+ } else {
+ /* server doesn't support STARTTLS, abort */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to connect to IMAP server %s in secure mode: %s"),
+ service->url->host, _("SSL/TLS extension not supported."));
+ /* we have the possibility of quitting cleanly here */
+ clean_quit = TRUE;
+ goto exception;
+ }
+ }
+ }
+#endif /* HAVE_SSL */
+
+ return TRUE;
+
+#ifdef HAVE_SSL
+ starttls:
+
+ /* as soon as we send a STARTTLS command, all hope is lost of a clean QUIT if problems arise */
+ clean_quit = FALSE;
+
+ response = camel_imap_command (store, NULL, ex, "STARTTLS");
+ if (!response) {
+ camel_object_unref (store->istream);
+ camel_object_unref (store->ostream);
+ store->istream = store->ostream = NULL;
+ return FALSE;
+ }
+
+ camel_imap_response_free_without_processing (store, response);
+
+ /* Okay, now toggle SSL/TLS mode */
+ if (camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream)) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to connect to IMAP server %s in secure mode: %s"),
+ service->url->host, _("SSL negotiations failed"));
+ goto exception;
+ }
+
+ /* rfc2595, section 4 states that after a successful STLS
+ command, the client MUST discard prior CAPA responses */
+ if (!imap_get_capability (service, ex)) {
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ store->connected = FALSE;
+
+ return FALSE;
+ }
+
+ return TRUE;
+
+ exception:
+
+ if (clean_quit && store->connected) {
+ /* try to disconnect cleanly */
+ response = camel_imap_command (store, NULL, ex, "LOGOUT");
+ if (response)
+ camel_imap_response_free_without_processing (store, response);
+ }
+
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ store->connected = FALSE;
+
+ return FALSE;
+#endif /* HAVE_SSL */
+}
+
+static gboolean
+connect_to_server_process (CamelService *service, const char *cmd, CamelException *ex)
+{
+ CamelImapStore *store = (CamelImapStore *) service;
+ CamelStream *cmd_stream;
+ int ret, i = 0;
+ char *buf;
+ char *cmd_copy;
+ char *full_cmd;
+ char *child_env[7];
+
+ /* Put full details in the environment, in case the connection
+ program needs them */
+ buf = camel_url_to_string(service->url, 0);
+ child_env[i++] = g_strdup_printf("URL=%s", buf);
+ g_free(buf);
+
+ child_env[i++] = g_strdup_printf("URLHOST=%s", service->url->host);
+ if (service->url->port)
+ child_env[i++] = g_strdup_printf("URLPORT=%d", service->url->port);
+ if (service->url->user)
+ child_env[i++] = g_strdup_printf("URLUSER=%s", service->url->user);
+ if (service->url->passwd)
+ child_env[i++] = g_strdup_printf("URLPASSWD=%s", service->url->passwd);
+ if (service->url->path)
+ child_env[i++] = g_strdup_printf("URLPATH=%s", service->url->path);
+ child_env[i] = NULL;
+
+ /* Now do %h, %u, etc. substitution in cmd */
+ buf = cmd_copy = g_strdup(cmd);
+
+ full_cmd = g_strdup("");
+
+ for(;;) {
+ char *pc;
+ char *tmp;
+ char *var;
+ int len;
+
+ pc = strchr(buf, '%');
+ ignore:
+ if (!pc) {
+ tmp = g_strdup_printf("%s%s", full_cmd, buf);
+ g_free(full_cmd);
+ full_cmd = tmp;
+ break;
+ }
+
+ len = pc - buf;
+
+ var = NULL;
+
+ switch(pc[1]) {
+ case 'h':
+ var = service->url->host;
+ break;
+ case 'u':
+ var = service->url->user;
+ break;
+ }
+ if (!var) {
+ /* If there wasn't a valid %-code, with an actual
+ variable to insert, pretend we didn't see the % */
+ pc = strchr(pc + 1, '%');
+ goto ignore;
+ }
+ tmp = g_strdup_printf("%s%.*s%s", full_cmd, len, buf, var);
+ g_free(full_cmd);
+ full_cmd = tmp;
+ buf = pc + 2;
+ }
+
+ g_free(cmd_copy);
+
+ cmd_stream = camel_stream_process_new ();
+
+ ret = camel_stream_process_connect (CAMEL_STREAM_PROCESS(cmd_stream),
+ full_cmd, (const char **)child_env);
+
+ while (i)
+ g_free(child_env[--i]);
+
+ if (ret == -1) {
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("Connection cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect with command \"%s\": %s"),
+ full_cmd, g_strerror (errno));
+
+ camel_object_unref (cmd_stream);
+ g_free (full_cmd);
+ return FALSE;
+ }
+ g_free (full_cmd);
+
+ store->ostream = cmd_stream;
+ store->istream = camel_stream_buffer_new (cmd_stream, CAMEL_STREAM_BUFFER_READ);
+
+ store->connected = TRUE;
+ store->preauthed = FALSE;
+ store->command = 0;
+
+ /* Read the greeting, if any, and deal with PREAUTH */
+ if (camel_imap_store_readline (store, &buf, ex) < 0) {
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ store->connected = FALSE;
+ return FALSE;
+ }
+ if (!strncmp(buf, "* PREAUTH", 9))
+ store->preauthed = TRUE;
+ g_free (buf);
+
+ /* get the imap server capabilities */
+ if (!imap_get_capability (service, ex)) {
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ store->connected = FALSE;
+ return FALSE;
+ }
+
+ return TRUE;
+
+}
+
+static struct {
+ char *value;
+ int mode;
+} ssl_options[] = {
+ { "", USE_SSL_ALWAYS },
+ { "always", USE_SSL_ALWAYS },
+ { "when-possible", USE_SSL_WHEN_POSSIBLE },
+ { "never", USE_SSL_NEVER },
+ { NULL, USE_SSL_NEVER },
+};
+
+static gboolean
+connect_to_server_wrapper (CamelService *service, CamelException *ex)
+{
+ const char *command;
+#ifdef HAVE_SSL
+ const char *use_ssl;
+ int i, ssl_mode;
+#endif
+ command = camel_url_get_param (service->url, "command");
+ if (command)
+ return connect_to_server_process (service, command, ex);
+
+#ifdef HAVE_SSL
+ use_ssl = camel_url_get_param (service->url, "use_ssl");
+ if (use_ssl) {
+ for (i = 0; ssl_options[i].value; i++)
+ if (!strcmp (ssl_options[i].value, use_ssl))
+ break;
+ ssl_mode = ssl_options[i].mode;
+ } else
+ ssl_mode = USE_SSL_NEVER;
+
+ if (ssl_mode == USE_SSL_ALWAYS) {
+ /* First try the ssl port */
+ if (!connect_to_server (service, ssl_mode, FALSE, ex)) {
+ if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) {
+ /* The ssl port seems to be unavailable, lets try STARTTLS */
+ camel_exception_clear (ex);
+ return connect_to_server (service, ssl_mode, TRUE, ex);
+ } else {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
+ /* If the server supports STARTTLS, use it */
+ return connect_to_server (service, ssl_mode, TRUE, ex);
+ } else {
+ /* User doesn't care about SSL */
+ return connect_to_server (service, ssl_mode, FALSE, ex);
+ }
+#else
+ return connect_to_server (service, USE_SSL_NEVER, FALSE, ex);
+#endif
+}
+
+extern CamelServiceAuthType camel_imap_password_authtype;
+
+static GList *
+query_auth_types (CamelService *service, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelServiceAuthType *authtype;
+ GList *sasl_types, *t, *next;
+ gboolean connected;
+
+ if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
+ return NULL;
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+ connected = connect_to_server_wrapper (service, ex);
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ if (!connected)
+ return NULL;
+
+ sasl_types = camel_sasl_authtype_list (FALSE);
+ for (t = sasl_types; t; t = next) {
+ authtype = t->data;
+ next = t->next;
+
+ if (!g_hash_table_lookup (store->authtypes, authtype->authproto)) {
+ sasl_types = g_list_remove_link (sasl_types, t);
+ g_list_free_1 (t);
+ }
+ }
+
+ return g_list_prepend (sasl_types, &camel_imap_password_authtype);
+}
+
+/* folder_name is path name */
+static CamelFolderInfo *
+imap_build_folder_info(CamelImapStore *imap_store, const char *folder_name)
+{
+ CamelURL *url;
+ const char *name;
+ CamelFolderInfo *fi;
+
+ fi = g_malloc0(sizeof(*fi));
+
+ fi->full_name = g_strdup(folder_name);
+ fi->unread = 0;
+ fi->total = 0;
+
+ url = camel_url_new (imap_store->base_url, NULL);
+ g_free (url->path);
+ url->path = g_strdup_printf ("/%s", folder_name);
+ fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+ camel_url_free(url);
+ name = strrchr (fi->full_name, '/');
+ if (name == NULL)
+ name = fi->full_name;
+ else
+ name++;
+ fi->name = g_strdup (name);
+
+ return fi;
+}
+
+static void
+imap_folder_effectively_unsubscribed(CamelImapStore *imap_store,
+ const char *folder_name, CamelException *ex)
+{
+ CamelFolderInfo *fi;
+ CamelStoreInfo *si;
+
+ si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
+ if (si) {
+ if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
+ si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+ camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
+ camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+
+ if (imap_store->renaming) {
+ /* we don't need to emit a "folder_unsubscribed" signal
+ if we are in the process of renaming folders, so we
+ are done here... */
+ return;
+
+ }
+
+ fi = imap_build_folder_info(imap_store, folder_name);
+ camel_object_trigger_event (CAMEL_OBJECT (imap_store), "folder_unsubscribed", fi);
+ camel_folder_info_free (fi);
+}
+
+static void
+imap_forget_folder (CamelImapStore *imap_store, const char *folder_name, CamelException *ex)
+{
+ CamelFolderSummary *summary;
+ CamelImapMessageCache *cache;
+ char *summary_file, *state_file;
+ char *journal_file;
+ char *folder_dir, *storage_path;
+ CamelFolderInfo *fi;
+ const char *name;
+
+ name = strrchr (folder_name, imap_store->dir_sep);
+ if (name)
+ name++;
+ else
+ name = folder_name;
+
+ storage_path = g_strdup_printf ("%s/folders", imap_store->storage_path);
+ folder_dir = e_path_to_physical (storage_path, folder_name);
+ g_free (storage_path);
+ if (access (folder_dir, F_OK) != 0) {
+ g_free (folder_dir);
+ goto event;
+ }
+
+ summary_file = g_strdup_printf ("%s/summary", folder_dir);
+ summary = camel_imap_summary_new (summary_file);
+ if (!summary) {
+ g_free (summary_file);
+ g_free (folder_dir);
+ goto event;
+ }
+
+ cache = camel_imap_message_cache_new (folder_dir, summary, ex);
+ if (cache)
+ camel_imap_message_cache_clear (cache);
+
+ camel_object_unref (cache);
+ camel_object_unref (summary);
+
+ unlink (summary_file);
+ g_free (summary_file);
+
+ journal_file = g_strdup_printf ("%s/journal", folder_dir);
+ unlink (journal_file);
+ g_free (journal_file);
+
+ state_file = g_strdup_printf ("%s/cmeta", folder_dir);
+ unlink (state_file);
+ g_free (state_file);
+
+ rmdir (folder_dir);
+ g_free (folder_dir);
+
+ event:
+
+ camel_store_summary_remove_path((CamelStoreSummary *)imap_store->summary, folder_name);
+ camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
+
+ fi = imap_build_folder_info(imap_store, folder_name);
+ camel_object_trigger_event (CAMEL_OBJECT (imap_store), "folder_deleted", fi);
+ camel_folder_info_free (fi);
+}
+
+static gboolean
+imap_check_folder_still_extant (CamelImapStore *imap_store, const char *full_name,
+ CamelException *ex)
+{
+ CamelImapResponse *response;
+
+ response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %F",
+ full_name);
+
+ if (response) {
+ gboolean stillthere = response->untagged->len != 0;
+
+ camel_imap_response_free_without_processing (imap_store, response);
+
+ return stillthere;
+ }
+
+ /* if the command was rejected, there must be some other error,
+ assume it worked so we dont blow away the folder unecessarily */
+ return TRUE;
+}
+
+/* This is a little 'hack' to avoid the deadlock conditions that would otherwise
+ ensue when calling camel_folder_refresh_info from inside a lock */
+/* NB: on second thougts this is probably not entirely safe, but it'll do for now */
+/* No, its definetly not safe. So its been changed to copy the folders first */
+/* the alternative is to:
+ make the camel folder->lock recursive (which should probably be done)
+ or remove it from camel_folder_refresh_info, and use another locking mechanism */
+/* also see get_folder_info_online() for the same hack repeated */
+static void
+imap_store_refresh_folders (CamelImapStore *store, CamelException *ex)
+{
+ GPtrArray *folders;
+ int i;
+
+ folders = camel_object_bag_list(CAMEL_STORE (store)->folders);
+
+ for (i = 0; i <folders->len; i++) {
+ CamelFolder *folder = folders->pdata[i];
+
+ /* NB: we can have vtrash folders also in our store ... bit hacky */
+ if (!CAMEL_IS_IMAP_FOLDER(folder)) {
+ camel_object_unref(folder);
+ continue;
+ }
+
+ CAMEL_IMAP_FOLDER (folder)->need_rescan = TRUE;
+ if (!camel_exception_is_set(ex))
+ CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->refresh_info(folder, ex);
+
+ if (camel_exception_is_set (ex) &&
+ imap_check_folder_still_extant (store, folder->full_name, ex) == FALSE) {
+ gchar *namedup;
+
+ /* the folder was deleted (may happen when we come back online
+ * after being offline */
+
+ namedup = g_strdup (folder->full_name);
+ camel_object_unref(folder);
+ imap_folder_effectively_unsubscribed (store, namedup, ex);
+ imap_forget_folder (store, namedup, ex);
+ g_free (namedup);
+ } else
+ camel_object_unref(folder);
+ }
+
+ g_ptr_array_free (folders, TRUE);
+}
+
+static gboolean
+try_auth (CamelImapStore *store, const char *mech, CamelException *ex)
+{
+ CamelSasl *sasl;
+ CamelImapResponse *response;
+ char *resp;
+ char *sasl_resp;
+
+ CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
+
+ response = camel_imap_command (store, NULL, ex, "AUTHENTICATE %s", mech);
+ if (!response)
+ return FALSE;
+
+ sasl = camel_sasl_new ("imap", mech, CAMEL_SERVICE (store));
+ while (!camel_sasl_authenticated (sasl)) {
+ resp = camel_imap_response_extract_continuation (store, response, ex);
+ if (!resp)
+ goto lose;
+
+ sasl_resp = camel_sasl_challenge_base64 (sasl, imap_next_word (resp), ex);
+ g_free (resp);
+ if (camel_exception_is_set (ex))
+ goto break_and_lose;
+
+ response = camel_imap_command_continuation (store, sasl_resp, strlen (sasl_resp), ex);
+ g_free (sasl_resp);
+ if (!response)
+ goto lose;
+ }
+
+ resp = camel_imap_response_extract_continuation (store, response, NULL);
+ if (resp) {
+ /* Oops. SASL claims we're done, but the IMAP server
+ * doesn't think so...
+ */
+ g_free (resp);
+ goto lose;
+ }
+
+ camel_object_unref (sasl);
+
+ return TRUE;
+
+ break_and_lose:
+ /* Get the server out of "waiting for continuation data" mode. */
+ response = camel_imap_command_continuation (store, "*", 1, NULL);
+ if (response)
+ camel_imap_response_free (store, response);
+
+ lose:
+ if (!camel_exception_is_set (ex)) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Bad authentication response from server."));
+ }
+
+ camel_object_unref (sasl);
+
+ return FALSE;
+}
+
+static gboolean
+imap_auth_loop (CamelService *service, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelSession *session = camel_service_get_session (service);
+ CamelServiceAuthType *authtype = NULL;
+ CamelImapResponse *response;
+ char *errbuf = NULL;
+ gboolean authenticated = FALSE;
+ const char *auth_domain;
+
+ CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
+ auth_domain = camel_url_get_param (service->url, "auth-domain");
+
+ if (store->preauthed) {
+ if (camel_verbose_debug)
+ fprintf(stderr, "Server %s has preauthenticated us.\n",
+ service->url->host);
+ return TRUE;
+ }
+
+ if (service->url->authmech) {
+ if (!g_hash_table_lookup (store->authtypes, service->url->authmech)) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("IMAP server %s does not support requested "
+ "authentication type %s"),
+ service->url->host,
+ service->url->authmech);
+ return FALSE;
+ }
+
+ authtype = camel_sasl_authtype (service->url->authmech);
+ if (!authtype) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("No support for authentication type %s"),
+ service->url->authmech);
+ return FALSE;
+ }
+
+ if (!authtype->need_password) {
+ authenticated = try_auth (store, authtype->authproto, ex);
+ if (!authenticated)
+ return FALSE;
+ }
+ }
+
+ while (!authenticated) {
+ if (errbuf) {
+ /* We need to un-cache the password before prompting again */
+ camel_session_forget_password (session, service, auth_domain, "password", ex);
+ g_free (service->url->passwd);
+ service->url->passwd = NULL;
+ }
+
+ if (!service->url->passwd) {
+ char *prompt;
+
+ prompt = g_strdup_printf (_("%sPlease enter the IMAP "
+ "password for %s@%s"),
+ errbuf ? errbuf : "",
+ service->url->user,
+ service->url->host);
+ service->url->passwd =
+ camel_session_get_password (session, service, auth_domain,
+ prompt, "password", CAMEL_SESSION_PASSWORD_SECRET, ex);
+ g_free (prompt);
+ g_free (errbuf);
+ errbuf = NULL;
+
+ if (!service->url->passwd) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+ _("You didn't enter a password."));
+ return FALSE;
+ }
+ }
+
+ if (!store->connected) {
+ /* Some servers (eg, courier) will disconnect on
+ * a bad password. So reconnect here.
+ */
+ if (!connect_to_server_wrapper (service, ex))
+ return FALSE;
+ }
+
+ if (authtype)
+ authenticated = try_auth (store, authtype->authproto, ex);
+ else {
+ response = camel_imap_command (store, NULL, ex,
+ "LOGIN %S %S",
+ service->url->user,
+ service->url->passwd);
+ if (response) {
+ camel_imap_response_free (store, response);
+ authenticated = TRUE;
+ }
+ }
+ if (!authenticated) {
+ if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_USER_CANCEL)
+ return FALSE;
+
+ errbuf = g_strdup_printf (_("Unable to authenticate "
+ "to IMAP server.\n%s\n\n"),
+ camel_exception_get_description (ex));
+ camel_exception_clear (ex);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+can_work_offline (CamelDiscoStore *disco_store)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (disco_store);
+
+ return camel_store_summary_count((CamelStoreSummary *)store->summary) != 0;
+}
+
+static gboolean
+imap_connect_online (CamelService *service, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelDiscoStore *disco_store = CAMEL_DISCO_STORE (service);
+ CamelImapResponse *response;
+ /*struct _namespaces *namespaces;*/
+ char *result, *name, *path;
+ int i;
+ size_t len;
+ CamelImapStoreNamespace *ns;
+
+ CAMEL_SERVICE_LOCK (store, connect_lock);
+ if (!connect_to_server_wrapper (service, ex) ||
+ !imap_auth_loop (service, ex)) {
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+ camel_service_disconnect (service, TRUE, NULL);
+ return FALSE;
+ }
+
+ /* Get namespace and hierarchy separator */
+ if ((store->capabilities & IMAP_CAPABILITY_NAMESPACE) &&
+ !(store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE)) {
+ response = camel_imap_command (store, NULL, ex, "NAMESPACE");
+ if (!response)
+ goto done;
+
+ result = camel_imap_response_extract (store, response, "NAMESPACE", ex);
+ if (!result)
+ goto done;
+
+#if 0
+ /* new code... */
+ namespaces = imap_parse_namespace_response (result);
+ imap_namespaces_destroy (namespaces);
+ /* end new code */
+#endif
+
+ name = camel_strstrcase (result, "NAMESPACE ((");
+ if (name) {
+ char *sep;
+
+ name += 12;
+ store->namespace = imap_parse_string ((const char **) &name, &len);
+ if (name && *name++ == ' ') {
+ sep = imap_parse_string ((const char **) &name, &len);
+ if (sep) {
+ store->dir_sep = *sep;
+ g_free (sep);
+ }
+ }
+ }
+ g_free (result);
+ }
+
+ if (!store->namespace)
+ store->namespace = g_strdup ("");
+
+ if (!store->dir_sep) {
+ if (store->server_level >= IMAP_LEVEL_IMAP4REV1) {
+ /* This idiom means "tell me the hierarchy separator
+ * for the given path, even if that path doesn't exist.
+ */
+ response = camel_imap_command (store, NULL, ex,
+ "LIST %S \"\"",
+ store->namespace);
+ } else {
+ /* Plain IMAP4 doesn't have that idiom, so we fall back
+ * to "tell me about this folder", which will fail if
+ * the folder doesn't exist (eg, if namespace is "").
+ */
+ response = camel_imap_command (store, NULL, ex,
+ "LIST \"\" %S",
+ store->namespace);
+ }
+ if (!response)
+ goto done;
+
+ result = camel_imap_response_extract (store, response, "LIST", NULL);
+ if (result) {
+ imap_parse_list_response (store, result, NULL, &store->dir_sep, NULL);
+ g_free (result);
+ }
+ if (!store->dir_sep) {
+ store->dir_sep = '/'; /* Guess */
+ }
+ }
+
+ /* canonicalize the namespace to end with dir_sep */
+ len = strlen (store->namespace);
+ if (len && store->namespace[len - 1] != store->dir_sep) {
+ gchar *tmp;
+
+ tmp = g_strdup_printf ("%s%c", store->namespace, store->dir_sep);
+ g_free (store->namespace);
+ store->namespace = tmp;
+ }
+
+ ns = camel_imap_store_summary_namespace_new(store->summary, store->namespace, store->dir_sep);
+ camel_imap_store_summary_namespace_set(store->summary, ns);
+
+ if (CAMEL_STORE (store)->flags & CAMEL_STORE_SUBSCRIPTIONS) {
+ gboolean haveinbox = FALSE;
+ GPtrArray *folders;
+ char *pattern;
+
+ /* this pre-fills the summary, and checks that lsub is useful */
+ folders = g_ptr_array_new ();
+ pattern = g_strdup_printf ("%s*", store->namespace);
+ get_folders_online (store, pattern, folders, TRUE, ex);
+ g_free (pattern);
+
+ for (i = 0; i < folders->len; i++) {
+ CamelFolderInfo *fi = folders->pdata[i];
+
+ haveinbox = haveinbox || !strcasecmp (fi->full_name, "INBOX");
+
+ if (fi->flags & (CAMEL_IMAP_FOLDER_MARKED | CAMEL_IMAP_FOLDER_UNMARKED))
+ store->capabilities |= IMAP_CAPABILITY_useful_lsub;
+ camel_folder_info_free (fi);
+ }
+
+ /* if the namespace is under INBOX, check INBOX explicitly */
+ if (!g_ascii_strncasecmp (store->namespace, "INBOX", 5) && !camel_exception_is_set (ex)) {
+ gboolean just_subscribed = FALSE;
+ gboolean need_subscribe = FALSE;
+
+ recheck:
+ g_ptr_array_set_size (folders, 0);
+ get_folders_online (store, "INBOX", folders, TRUE, ex);
+
+ for (i = 0; i < folders->len; i++) {
+ CamelFolderInfo *fi = folders->pdata[i];
+
+ /* this should always be TRUE if folders->len > 0 */
+ if (!strcasecmp (fi->full_name, "INBOX")) {
+ haveinbox = TRUE;
+
+ /* if INBOX is marked as \NoSelect then it is probably
+ because it has not been subscribed to */
+ if (!need_subscribe)
+ need_subscribe = fi->flags & CAMEL_FOLDER_NOSELECT;
+ }
+
+ camel_folder_info_free (fi);
+ }
+
+ need_subscribe = !haveinbox || need_subscribe;
+ if (need_subscribe && !just_subscribed && !camel_exception_is_set (ex)) {
+ /* in order to avoid user complaints, force a subscription to INBOX */
+ response = camel_imap_command (store, NULL, ex, "SUBSCRIBE INBOX");
+ if (response != NULL) {
+ /* force a re-check which will pre-fill the summary and
+ also get any folder flags present on the INBOX */
+ camel_imap_response_free (store, response);
+ just_subscribed = TRUE;
+ goto recheck;
+ }
+ }
+ }
+
+ g_ptr_array_free (folders, TRUE);
+ }
+
+ path = g_strdup_printf ("%s/journal", store->storage_path);
+ disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
+ g_free (path);
+
+ done:
+ /* save any changes we had */
+ camel_store_summary_save((CamelStoreSummary *)store->summary);
+
+ CAMEL_SERVICE_UNLOCK (store, connect_lock);
+
+ if (camel_exception_is_set (ex))
+ camel_service_disconnect (service, TRUE, NULL);
+ else if (camel_disco_diary_empty (disco_store->diary))
+ imap_store_refresh_folders (store, ex);
+
+ return !camel_exception_is_set (ex);
+}
+
+static gboolean
+imap_connect_offline (CamelService *service, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelDiscoStore *disco_store = CAMEL_DISCO_STORE (service);
+ char *path;
+
+ path = g_strdup_printf ("%s/journal", store->storage_path);
+ disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
+ g_free (path);
+ if (!disco_store->diary)
+ return FALSE;
+
+ imap_store_refresh_folders (store, ex);
+
+ store->connected = !camel_exception_is_set (ex);
+ return store->connected;
+}
+
+static gboolean
+imap_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelDiscoStore *disco = CAMEL_DISCO_STORE (service);
+
+ store->connected = FALSE;
+ if (store->current_folder) {
+ camel_object_unref (store->current_folder);
+ store->current_folder = NULL;
+ }
+
+ if (store->authtypes) {
+ g_hash_table_foreach_remove (store->authtypes,
+ free_key, NULL);
+ g_hash_table_destroy (store->authtypes);
+ store->authtypes = NULL;
+ }
+
+ if (store->namespace && !(store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE)) {
+ g_free (store->namespace);
+ store->namespace = NULL;
+ }
+
+ if (disco->diary) {
+ camel_object_unref (disco->diary);
+ disco->diary = NULL;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+imap_disconnect_online (CamelService *service, gboolean clean, CamelException *ex)
+{
+ CamelImapStore *store = CAMEL_IMAP_STORE (service);
+ CamelImapResponse *response;
+
+ if (store->connected && clean) {
+ response = camel_imap_command (store, NULL, NULL, "LOGOUT");
+ camel_imap_response_free (store, response);
+ }
+
+ if (store->istream) {
+ camel_object_unref (store->istream);
+ store->istream = NULL;
+ }
+
+ if (store->ostream) {
+ camel_object_unref (store->ostream);
+ store->ostream = NULL;
+ }
+
+ imap_disconnect_offline (service, clean, ex);
+
+ return TRUE;
+}
+
+
+static gboolean
+imap_summary_is_dirty (CamelFolderSummary *summary)
+{
+ CamelMessageInfo *info;
+ int max, i;
+
+ max = camel_folder_summary_count (summary);
+ for (i = 0; i < max; i++) {
+ info = camel_folder_summary_index (summary, i);
+ if (info && (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+imap_noop (CamelStore *store, CamelException *ex)
+{
+ CamelImapStore *imap_store = (CamelImapStore *) store;
+ CamelDiscoStore *disco = (CamelDiscoStore *) store;
+ CamelImapResponse *response;
+ CamelFolder *current_folder;
+
+ if (camel_disco_store_status (disco) != CAMEL_DISCO_STORE_ONLINE)
+ return;
+
+ CAMEL_SERVICE_LOCK (imap_store, connect_lock);
+
+ current_folder = imap_store->current_folder;
+ if (current_folder && imap_summary_is_dirty (current_folder->summary)) {
+ /* let's sync the flags instead. NB: must avoid folder lock */
+ ((CamelFolderClass *)((CamelObject *)current_folder)->klass)->sync(current_folder, FALSE, ex);
+ } else {
+ response = camel_imap_command (imap_store, NULL, ex, "NOOP");
+ if (response)
+ camel_imap_response_free (imap_store, response);
+ }
+
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+}
+
+static CamelFolder *
+imap_get_trash(CamelStore *store, CamelException *ex)
+{
+ CamelFolder *folder = CAMEL_STORE_CLASS(parent_class)->get_trash(store, ex);
+
+ if (folder) {
+ char *state = g_build_filename(((CamelImapStore *)store)->storage_path, "system", "Trash.cmeta", NULL);
+
+ camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state, NULL);
+ g_free(state);
+ /* no defaults? */
+ camel_object_state_read(folder);
+ }
+
+ return folder;
+}
+
+static CamelFolder *
+imap_get_junk(CamelStore *store, CamelException *ex)
+{
+ CamelFolder *folder = CAMEL_STORE_CLASS(parent_class)->get_junk(store, ex);
+
+ if (folder) {
+ char *state = g_build_filename(((CamelImapStore *)store)->storage_path, "system", "Junk.cmeta", NULL);
+
+ camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state, NULL);
+ g_free(state);
+ /* no defaults? */
+ camel_object_state_read(folder);
+ }
+
+ return folder;
+}
+
+static guint
+hash_folder_name (gconstpointer key)
+{
+ if (g_ascii_strcasecmp (key, "INBOX") == 0)
+ return g_str_hash ("INBOX");
+ else
+ return g_str_hash (key);
+}
+
+static gint
+compare_folder_name (gconstpointer a, gconstpointer b)
+{
+ gconstpointer aname = a, bname = b;
+
+ if (g_ascii_strcasecmp (a, "INBOX") == 0)
+ aname = "INBOX";
+ if (g_ascii_strcasecmp (b, "INBOX") == 0)
+ bname = "INBOX";
+ return g_str_equal (aname, bname);
+}
+
+struct imap_status_item {
+ struct imap_status_item *next;
+ char *name;
+ guint32 value;
+};
+
+static void
+imap_status_item_free (struct imap_status_item *items)
+{
+ struct imap_status_item *next;
+
+ while (items != NULL) {
+ next = items->next;
+ g_free (items->name);
+ g_free (items);
+ items = next;
+ }
+}
+
+static struct imap_status_item *
+get_folder_status (CamelImapStore *imap_store, const char *folder_name, const char *type)
+{
+ struct imap_status_item *items, *item, *tail;
+ CamelImapResponse *response;
+ char *status, *name, *p;
+
+ /* FIXME: we assume the server is STATUS-capable */
+
+ response = camel_imap_command (imap_store, NULL, NULL,
+ "STATUS %F (%s)",
+ folder_name,
+ type);
+
+ if (!response) {
+ CamelException ex;
+
+ camel_exception_init (&ex);
+ if (imap_check_folder_still_extant (imap_store, folder_name, &ex) == FALSE) {
+ imap_folder_effectively_unsubscribed (imap_store, folder_name, &ex);
+ imap_forget_folder (imap_store, folder_name, &ex);
+ }
+ camel_exception_clear (&ex);
+ return NULL;
+ }
+
+ if (!(status = camel_imap_response_extract (imap_store, response, "STATUS", NULL)))
+ return NULL;
+
+ p = status + strlen ("* STATUS ");
+ while (*p == ' ')
+ p++;
+
+ /* skip past the mailbox string */
+ if (*p == '"') {
+ p++;
+ while (*p != '\0') {
+ if (*p == '"' && p[-1] != '\\') {
+ p++;
+ break;
+ }
+
+ p++;
+ }
+ } else {
+ while (*p != ' ')
+ p++;
+ }
+
+ while (*p == ' ')
+ p++;
+
+ if (*p++ != '(') {
+ g_free (status);
+ return NULL;
+ }
+
+ while (*p == ' ')
+ p++;
+
+ if (*p == ')') {
+ g_free (status);
+ return NULL;
+ }
+
+ items = NULL;
+ tail = (struct imap_status_item *) &items;
+
+ do {
+ name = p;
+ while (*p != ' ')
+ p++;
+
+ item = g_malloc (sizeof (struct imap_status_item));
+ item->next = NULL;
+ item->name = g_strndup (name, p - name);
+ item->value = strtoul (p, &p, 10);
+
+ tail->next = item;
+ tail = item;
+
+ while (*p == ' ')
+ p++;
+ } while (*p != ')');
+
+ g_free (status);
+
+ return items;
+}
+
+static CamelFolder *
+get_folder_online (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelImapResponse *response;
+ CamelFolder *new_folder;
+ char *folder_dir, *storage_path;
+
+ if (!camel_imap_store_connected (imap_store, ex))
+ return NULL;
+
+ if (!strcasecmp (folder_name, "INBOX"))
+ folder_name = "INBOX";
+
+ /* Lock around the whole lot to check/create atomically */
+ CAMEL_SERVICE_LOCK (imap_store, connect_lock);
+ if (imap_store->current_folder) {
+ camel_object_unref (imap_store->current_folder);
+ imap_store->current_folder = NULL;
+ }
+ response = camel_imap_command (imap_store, NULL, ex, "SELECT %F", folder_name);
+ if (!response) {
+ char *folder_real, *parent_name, *parent_real;
+ const char *c;
+
+ if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_USER_CANCEL) {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ return NULL;
+ }
+
+ camel_exception_clear (ex);
+
+ if (!(flags & CAMEL_STORE_FOLDER_CREATE)) {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("No such folder %s"), folder_name);
+ return NULL;
+ }
+
+ if ((parent_name = strrchr (folder_name, '/'))) {
+ parent_name = g_strndup (folder_name, parent_name - folder_name);
+ parent_real = camel_imap_store_summary_path_to_full (imap_store->summary, parent_name, imap_store->dir_sep);
+ } else {
+ parent_real = NULL;
+ }
+
+ c = parent_name ? parent_name : folder_name;
+ while (*c && *c != imap_store->dir_sep && !strchr ("#%*", *c))
+ c++;
+
+ if (*c != '\0') {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_PATH,
+ _("The folder name \"%s\" is invalid because it contains the character \"%c\""),
+ folder_name, *c);
+ g_free (parent_name);
+ g_free (parent_real);
+ return NULL;
+ }
+
+ if (parent_real != NULL) {
+ gboolean need_convert = FALSE;
+ char *resp, *thisone;
+ guint32 flags;
+ int i;
+
+ if (!(response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %S", parent_real))) {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ g_free (parent_name);
+ g_free (parent_real);
+ return NULL;
+ }
+
+ /* FIXME: does not handle unexpected circumstances very well */
+ for (i = 0; i < response->untagged->len; i++) {
+ resp = response->untagged->pdata[i];
+
+ if (!imap_parse_list_response (imap_store, resp, &flags, NULL, &thisone))
+ continue;
+
+ if (!strcmp (parent_name, thisone)) {
+ if (flags & CAMEL_FOLDER_NOINFERIORS)
+ need_convert = TRUE;
+ }
+
+ g_free (thisone);
+ }
+
+ camel_imap_response_free (imap_store, response);
+
+ /* if not, check if we can delete it and recreate it */
+ if (need_convert) {
+ struct imap_status_item *items, *item;
+ guint32 messages = 0;
+ CamelException lex;
+ char *name;
+
+ item = items = get_folder_status (imap_store, parent_name, "MESSAGES");
+ while (item != NULL) {
+ if (!g_ascii_strcasecmp (item->name, "MESSAGES")) {
+ messages = item->value;
+ break;
+ }
+
+ item = item->next;
+ }
+
+ imap_status_item_free (items);
+
+ if (messages > 0) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE,
+ _("The parent folder is not allowed to contain subfolders"));
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ g_free (parent_name);
+ g_free (parent_real);
+ return NULL;
+ }
+
+ /* delete the old parent and recreate it */
+ camel_exception_init (&lex);
+ delete_folder (store, parent_name, &lex);
+ if (camel_exception_is_set (&lex)) {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ camel_exception_xfer (ex, &lex);
+ g_free (parent_name);
+ g_free (parent_real);
+ return NULL;
+ }
+
+ /* add the dirsep to the end of parent_name */
+ name = g_strdup_printf ("%s%c", parent_real, imap_store->dir_sep);
+ response = camel_imap_command (imap_store, NULL, ex, "CREATE %S",
+ name);
+ g_free (name);
+
+ if (!response) {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ g_free (parent_name);
+ g_free (parent_real);
+ return NULL;
+ } else
+ camel_imap_response_free (imap_store, response);
+ }
+
+ g_free (parent_real);
+ }
+
+ g_free (parent_name);
+
+ folder_real = camel_imap_store_summary_path_to_full(imap_store->summary, folder_name, imap_store->dir_sep);
+ response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", folder_real);
+
+ if (response) {
+ camel_imap_store_summary_add_from_full(imap_store->summary, folder_real, imap_store->dir_sep);
+
+ camel_imap_response_free (imap_store, response);
+
+ response = camel_imap_command (imap_store, NULL, NULL, "SELECT %F", folder_name);
+ }
+ g_free(folder_real);
+ if (!response) {
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ return NULL;
+ }
+ } else if (flags & CAMEL_STORE_FOLDER_EXCL) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot create folder `%s': folder exists."),
+ folder_name);
+
+ camel_imap_response_free_without_processing (imap_store, response);
+
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+
+ return NULL;
+ }
+
+ storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
+ folder_dir = e_path_to_physical (storage_path, folder_name);
+ g_free(storage_path);
+ new_folder = camel_imap_folder_new (store, folder_name, folder_dir, ex);
+ g_free (folder_dir);
+ if (new_folder) {
+ CamelException local_ex;
+
+ imap_store->current_folder = new_folder;
+ camel_object_ref (new_folder);
+ camel_exception_init (&local_ex);
+ camel_imap_folder_selected (new_folder, response, &local_ex);
+
+ if (camel_exception_is_set (&local_ex)) {
+ camel_exception_xfer (ex, &local_ex);
+ camel_object_unref (imap_store->current_folder);
+ imap_store->current_folder = NULL;
+ camel_object_unref (new_folder);
+ new_folder = NULL;
+ }
+ }
+ camel_imap_response_free_without_processing (imap_store, response);
+
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+
+ return new_folder;
+}
+
+static CamelFolder *
+get_folder_offline (CamelStore *store, const char *folder_name,
+ guint32 flags, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelFolder *new_folder;
+ char *folder_dir, *storage_path;
+
+ if (!imap_store->connected &&
+ !camel_service_connect (CAMEL_SERVICE (store), ex))
+ return NULL;
+
+ if (!strcasecmp (folder_name, "INBOX"))
+ folder_name = "INBOX";
+
+ storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
+ folder_dir = e_path_to_physical (storage_path, folder_name);
+ g_free(storage_path);
+ if (!folder_dir || access (folder_dir, F_OK) != 0) {
+ g_free (folder_dir);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
+ _("No such folder %s"), folder_name);
+ return NULL;
+ }
+
+ new_folder = camel_imap_folder_new (store, folder_name, folder_dir, ex);
+ g_free (folder_dir);
+
+ return new_folder;
+}
+
+static void
+delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelImapResponse *response;
+
+ if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
+ return;
+
+ /* make sure this folder isn't currently SELECTed */
+ response = camel_imap_command (imap_store, NULL, ex, "SELECT INBOX");
+ if (response) {
+ camel_imap_response_free_without_processing (imap_store, response);
+
+ CAMEL_SERVICE_LOCK (imap_store, connect_lock);
+
+ if (imap_store->current_folder)
+ camel_object_unref (imap_store->current_folder);
+ /* no need to actually create a CamelFolder for INBOX */
+ imap_store->current_folder = NULL;
+
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ } else
+ return;
+
+ response = camel_imap_command (imap_store, NULL, ex, "DELETE %F",
+ folder_name);
+
+ if (response) {
+ camel_imap_response_free (imap_store, response);
+ imap_forget_folder (imap_store, folder_name, ex);
+ }
+}
+
+static void
+manage_subscriptions (CamelStore *store, const char *old_name, gboolean subscribe)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelStoreInfo *si;
+ int olen = strlen(old_name);
+ const char *path;
+ int i, count;
+
+ count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary);
+ for (i=0;i<count;i++) {
+ si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i);
+ if (si) {
+ path = camel_store_info_path(imap_store->summary, si);
+ if (strncmp(path, old_name, olen) == 0) {
+ if (subscribe)
+ subscribe_folder(store, path, NULL);
+ else
+ unsubscribe_folder(store, path, NULL);
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+ }
+}
+
+static void
+rename_folder_info (CamelImapStore *imap_store, const char *old_name, const char *new_name)
+{
+ int i, count;
+ CamelStoreInfo *si;
+ int olen = strlen(old_name);
+ const char *path;
+ char *npath, *nfull;
+
+ count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary);
+ for (i=0;i<count;i++) {
+ si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i);
+ if (si == NULL)
+ continue;
+ path = camel_store_info_path(imap_store->summary, si);
+ if (strncmp(path, old_name, olen) == 0) {
+ if (strlen(path) > olen)
+ npath = g_strdup_printf("%s/%s", new_name, path+olen+1);
+ else
+ npath = g_strdup(new_name);
+ nfull = camel_imap_store_summary_path_to_full(imap_store->summary, npath, imap_store->dir_sep);
+
+ /* workaround for broken server (courier uses '.') that doesn't rename
+ subordinate folders as required by rfc 2060 */
+ if (imap_store->dir_sep == '.') {
+ CamelImapResponse *response;
+
+ response = camel_imap_command (imap_store, NULL, NULL, "RENAME %F %S", path, nfull);
+ if (response)
+ camel_imap_response_free (imap_store, response);
+ }
+
+ camel_store_info_set_string((CamelStoreSummary *)imap_store->summary, si, CAMEL_STORE_INFO_PATH, npath);
+ camel_store_info_set_string((CamelStoreSummary *)imap_store->summary, si, CAMEL_IMAP_STORE_INFO_FULL_NAME, nfull);
+
+ camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
+ g_free(nfull);
+ g_free(npath);
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+}
+
+static void
+rename_folder (CamelStore *store, const char *old_name, const char *new_name_in, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelImapResponse *response;
+ char *oldpath, *newpath, *storage_path, *new_name;
+
+ if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
+ return;
+
+ /* make sure this folder isn't currently SELECTed - it's
+ actually possible to rename INBOX but if you do another
+ INBOX will immediately be created by the server */
+ response = camel_imap_command (imap_store, NULL, ex, "SELECT INBOX");
+ if (response) {
+ camel_imap_response_free_without_processing (imap_store, response);
+
+ CAMEL_SERVICE_LOCK (imap_store, connect_lock);
+
+ if (imap_store->current_folder)
+ camel_object_unref (imap_store->current_folder);
+ /* no need to actually create a CamelFolder for INBOX */
+ imap_store->current_folder = NULL;
+
+ CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
+ } else
+ return;
+
+ imap_store->renaming = TRUE;
+
+ if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
+ manage_subscriptions(store, old_name, FALSE);
+
+ new_name = camel_imap_store_summary_path_to_full(imap_store->summary, new_name_in, imap_store->dir_sep);
+ response = camel_imap_command (imap_store, NULL, ex, "RENAME %F %S", old_name, new_name);
+
+ if (!response) {
+ if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
+ manage_subscriptions(store, old_name, TRUE);
+ g_free(new_name);
+ imap_store->renaming = FALSE;
+ return;
+ }
+
+ camel_imap_response_free (imap_store, response);
+
+ /* rename summary, and handle broken server */
+ rename_folder_info(imap_store, old_name, new_name_in);
+
+ if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
+ manage_subscriptions(store, new_name_in, TRUE);
+
+ storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
+ oldpath = e_path_to_physical (storage_path, old_name);
+ newpath = e_path_to_physical (storage_path, new_name_in);
+ g_free(storage_path);
+
+ /* So do we care if this didn't work? Its just a cache? */
+ if (rename (oldpath, newpath) == -1) {
+ g_warning ("Could not rename message cache '%s' to '%s': %s: cache reset",
+ oldpath, newpath, strerror (errno));
+ }
+
+ g_free (oldpath);
+ g_free (newpath);
+ g_free(new_name);
+
+ imap_store->renaming = FALSE;
+}
+
+static CamelFolderInfo *
+create_folder (CamelStore *store, const char *parent_name,
+ const char *folder_name, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ char *full_name, *resp, *thisone, *parent_real, *real_name;
+ CamelImapResponse *response;
+ CamelException internal_ex;
+ CamelFolderInfo *root = NULL;
+ gboolean need_convert;
+ int i = 0, flags;
+ const char *c;
+
+ if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
+ return NULL;
+ if (!parent_name)
+ parent_name = "";
+
+ c = folder_name;
+ while (*c && *c != imap_store->dir_sep && !strchr ("#%*", *c))
+ c++;
+
+ if (*c != '\0') {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_PATH,
+ _("The folder name \"%s\" is invalid because it contains the character \"%c\""),
+ folder_name, *c);
+ return NULL;
+ }
+
+ /* check if the parent allows inferiors */
+
+ /* FIXME: use storesummary directly */
+ parent_real = camel_imap_store_summary_full_from_path(imap_store->summary, parent_name);
+ if (parent_real == NULL) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE,
+ _("Unknown parent folder: %s"), parent_name);
+ return NULL;
+ }
+
+ need_convert = FALSE;
+ response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %S",
+ parent_real);
+ if (!response) /* whoa, this is bad */ {
+ g_free(parent_real);
+ return NULL;
+ }
+
+ /* FIXME: does not handle unexpected circumstances very well */
+ for (i = 0; i < response->untagged->len; i++) {
+ resp = response->untagged->pdata[i];
+
+ if (!imap_parse_list_response (imap_store, resp, &flags, NULL, &thisone))
+ continue;
+
+ if (strcmp (thisone, parent_name) == 0) {
+ if (flags & CAMEL_FOLDER_NOINFERIORS)
+ need_convert = TRUE;
+ break;
+ }
+ }
+
+ camel_imap_response_free (imap_store, response);
+
+ camel_exception_init (&internal_ex);
+
+ /* if not, check if we can delete it and recreate it */
+ if (need_convert) {
+ struct imap_status_item *items, *item;
+ guint32 messages = 0;
+ char *name;
+
+ item = items = get_folder_status (imap_store, parent_name, "MESSAGES");
+ while (item != NULL) {
+ if (!g_ascii_strcasecmp (item->name, "MESSAGES")) {
+ messages = item->value;
+ break;
+ }
+
+ item = item->next;
+ }
+
+ imap_status_item_free (items);
+
+ if (messages > 0) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE,
+ _("The parent folder is not allowed to contain subfolders"));
+ g_free(parent_real);
+ return NULL;
+ }
+
+ /* delete the old parent and recreate it */
+ delete_folder (store, parent_name, &internal_ex);
+ if (camel_exception_is_set (&internal_ex)) {
+ camel_exception_xfer (ex, &internal_ex);
+ return NULL;
+ }
+
+ /* add the dirsep to the end of parent_name */
+ name = g_strdup_printf ("%s%c", parent_real, imap_store->dir_sep);
+ response = camel_imap_command (imap_store, NULL, ex, "CREATE %S",
+ name);
+ g_free (name);
+
+ if (!response) {
+ g_free(parent_real);
+ return NULL;
+ } else
+ camel_imap_response_free (imap_store, response);
+
+ root = imap_build_folder_info(imap_store, parent_name);
+ }
+
+ /* ok now we can create the folder */
+ real_name = camel_imap_store_summary_path_to_full(imap_store->summary, folder_name, imap_store->dir_sep);
+ full_name = imap_concat (imap_store, parent_real, real_name);
+ g_free(real_name);
+ response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", full_name);
+
+ if (response) {
+ CamelImapStoreInfo *si;
+ CamelFolderInfo *fi;
+
+ camel_imap_response_free (imap_store, response);
+
+ si = camel_imap_store_summary_add_from_full(imap_store->summary, full_name, imap_store->dir_sep);
+ camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
+ fi = imap_build_folder_info(imap_store, camel_store_info_path(imap_store->summary, si));
+ fi->flags |= CAMEL_FOLDER_NOCHILDREN;
+ if (root) {
+ root->child = fi;
+ fi->parent = root;
+ } else {
+ root = fi;
+ }
+ camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", root);
+ } else if (root) {
+ /* need to re-recreate the folder we just deleted */
+ camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", root);
+ camel_folder_info_free(root);
+ root = NULL;
+ }
+
+ g_free (full_name);
+ g_free(parent_real);
+
+ return root;
+}
+
+static CamelFolderInfo *
+parse_list_response_as_folder_info (CamelImapStore *imap_store,
+ const char *response)
+{
+ CamelFolderInfo *fi;
+ int flags;
+ char sep, *dir, *path;
+ CamelURL *url;
+ CamelImapStoreInfo *si;
+ guint32 newflags;
+
+ if (!imap_parse_list_response (imap_store, response, &flags, &sep, &dir))
+ return NULL;
+
+ /* FIXME: should use imap_build_folder_info, note the differences with param setting tho */
+
+ si = camel_imap_store_summary_add_from_full(imap_store->summary, dir, sep?sep:'/');
+ if (si == NULL)
+ return NULL;
+
+ newflags = (si->info.flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) | (flags & ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED);
+ if (si->info.flags != newflags) {
+ si->info.flags = newflags;
+ camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
+ }
+
+ fi = g_new0 (CamelFolderInfo, 1);
+ fi->name = g_strdup(camel_store_info_name(imap_store->summary, si));
+ fi->full_name = g_strdup(camel_store_info_path(imap_store->summary, si));
+ if (!g_ascii_strcasecmp(fi->full_name, "inbox"))
+ flags |= CAMEL_FOLDER_SYSTEM;
+ /* HACK: some servers report noinferiors for all folders (uw-imapd)
+ We just translate this into nochildren, and let the imap layer enforce
+ it. See create folder */
+ if (flags & CAMEL_FOLDER_NOINFERIORS)
+ flags = (fi->flags & ~CAMEL_FOLDER_NOINFERIORS) | CAMEL_FOLDER_NOCHILDREN;
+ fi->flags = flags;
+
+ url = camel_url_new (imap_store->base_url, NULL);
+ path = alloca(strlen(fi->full_name)+2);
+ sprintf(path, "/%s", fi->full_name);
+ camel_url_set_path(url, path);
+
+ if (flags & CAMEL_FOLDER_NOSELECT || fi->name[0] == 0)
+ camel_url_set_param (url, "noselect", "yes");
+ fi->uri = camel_url_to_string (url, 0);
+ camel_url_free (url);
+
+ /* FIXME: redundant */
+ if (flags & CAMEL_IMAP_FOLDER_UNMARKED)
+ fi->unread = -1;
+
+ return fi;
+}
+
+/* returns true if full_name is a sub-folder of top, or is top */
+static int
+imap_is_subfolder(const char *full_name, const char *top)
+{
+ size_t len = strlen(top);
+
+ /* Looks for top being a full-path subset of full_name.
+ Handle IMAP Inbox case insensitively */
+
+ if (g_ascii_strncasecmp(top, "inbox", 5) == 0
+ && (top[5] == 0 || top[5] == '/')
+ && g_ascii_strncasecmp(full_name, "inbox", 5) == 0
+ && (full_name[5] == 0 || full_name[5] == '/')) {
+ full_name += 5;
+ top += 5;
+ len -= 5;
+ }
+
+ return top[0] == 0
+ || (strncmp(full_name, top, len) == 0
+ && (full_name[len] == 0
+ || full_name[len] == '/'));
+}
+
+/* this is used when lsub doesn't provide very useful information */
+static GPtrArray *
+get_subscribed_folders (CamelImapStore *imap_store, const char *top, CamelException *ex)
+{
+ GPtrArray *names, *folders;
+ int i;
+ CamelStoreInfo *si;
+ CamelImapResponse *response;
+ CamelFolderInfo *fi;
+ char *result;
+ int haveinbox = FALSE;
+
+ if (camel_debug("imap:folder_info"))
+ printf(" get_subscribed folders\n");
+
+ folders = g_ptr_array_new ();
+ names = g_ptr_array_new ();
+ for (i=0;(si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i));i++) {
+ if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED
+ && imap_is_subfolder(camel_store_info_path(imap_store->summary, si), top)) {
+ g_ptr_array_add(names, (char *)camel_imap_store_info_full_name(imap_store->summary, si));
+ haveinbox = haveinbox || strcasecmp(camel_imap_store_info_full_name(imap_store->summary, si), "INBOX") == 0;
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+
+ if (!haveinbox)
+ g_ptr_array_add (names, "INBOX");
+
+ for (i = 0; i < names->len; i++) {
+ response = camel_imap_command (imap_store, NULL, ex,
+ "LIST \"\" %S",
+ names->pdata[i]);
+ if (!response)
+ break;
+
+ result = camel_imap_response_extract (imap_store, response, "LIST", NULL);
+ if (!result) {
+ camel_store_summary_remove_path((CamelStoreSummary *)imap_store->summary, names->pdata[i]);
+ g_ptr_array_remove_index_fast (names, i);
+ i--;
+ continue;
+ }
+
+ fi = parse_list_response_as_folder_info (imap_store, result);
+ g_free (result);
+ if (!fi)
+ continue;
+
+ if (!imap_is_subfolder(fi->full_name, top)) {
+ camel_folder_info_free (fi);
+ continue;
+ }
+
+ g_ptr_array_add (folders, fi);
+ }
+
+ g_ptr_array_free (names, TRUE);
+
+ return folders;
+}
+
+static int imap_match_pattern(char dir_sep, const char *pattern, const char *name)
+{
+ char p, n;
+
+ p = *pattern++;
+ n = *name++;
+ while (n && p) {
+ if (n == p) {
+ p = *pattern++;
+ n = *name++;
+ } else if (p == '%') {
+ if (n != dir_sep) {
+ n = *name++;
+ } else {
+ p = *pattern++;
+ }
+ } else if (p == '*') {
+ return TRUE;
+ } else
+ return FALSE;
+ }
+
+ return n == 0 && (p == '%' || p == 0);
+}
+
+static void
+get_folders_online (CamelImapStore *imap_store, const char *pattern,
+ GPtrArray *folders, gboolean lsub, CamelException *ex)
+{
+ CamelImapResponse *response;
+ CamelFolderInfo *fi;
+ char *list;
+ int i, count;
+ GHashTable *present;
+ CamelStoreInfo *si;
+
+ response = camel_imap_command (imap_store, NULL, ex,
+ "%s \"\" %S", lsub ? "LSUB" : "LIST",
+ pattern);
+ if (!response)
+ return;
+
+ present = g_hash_table_new(g_str_hash, g_str_equal);
+ for (i = 0; i < response->untagged->len; i++) {
+ list = response->untagged->pdata[i];
+ fi = parse_list_response_as_folder_info (imap_store, list);
+ if (fi) {
+ g_ptr_array_add(folders, fi);
+ g_hash_table_insert(present, fi->full_name, fi);
+ }
+ }
+ camel_imap_response_free (imap_store, response);
+
+ /* update our summary to match the server */
+ count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary);
+ for (i=0;i<count;i++) {
+ si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i);
+ if (si == NULL)
+ continue;
+
+ if (imap_match_pattern(imap_store->dir_sep, pattern, camel_imap_store_info_full_name(imap_store->summary, si))) {
+ if (g_hash_table_lookup(present, camel_store_info_path(imap_store->summary, si)) != NULL) {
+ if (lsub && (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) {
+ si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+ camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
+ }
+ } else {
+ if (lsub) {
+ if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
+ si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+ camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
+ }
+ } else {
+ camel_store_summary_remove((CamelStoreSummary *)imap_store->summary, si);
+ count--;
+ i--;
+ }
+ }
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+ g_hash_table_destroy(present);
+}
+
+#if 0
+static void
+dumpfi(CamelFolderInfo *fi)
+{
+ int depth;
+ CamelFolderInfo *n = fi;
+
+ if (fi == NULL)
+ return;
+
+ depth = 0;
+ while (n->parent) {
+ depth++;
+ n = n->parent;
+ }
+
+ while (fi) {
+ printf("%-40s %-30s %*s\n", fi->path, fi->full_name, depth*2+strlen(fi->url), fi->url);
+ if (fi->child)
+ dumpfi(fi->child);
+ fi = fi->sibling;
+ }
+}
+#endif
+
+static void
+fill_fi(CamelStore *store, CamelFolderInfo *fi, guint32 flags)
+{
+ CamelFolder *folder;
+
+ fi->unread = -1;
+ fi->total = -1;
+ folder = camel_object_bag_peek(store->folders, fi->full_name);
+ if (folder) {
+ if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
+ /* we use connect lock for everything, so this should be safe */
+ CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->refresh_info(folder, NULL);
+ fi->unread = camel_folder_get_unread_message_count(folder);
+ fi->total = camel_folder_get_message_count(folder);
+ camel_object_unref(folder);
+ } else {
+ char *storage_path, *folder_dir, *path;
+ CamelFolderSummary *s;
+
+ /* This is a lot of work for one path! */
+ storage_path = g_strdup_printf("%s/folders", ((CamelImapStore *)store)->storage_path);
+ folder_dir = e_path_to_physical(storage_path, fi->full_name);
+ path = g_strdup_printf("%s/summary", folder_dir);
+ s = (CamelFolderSummary *)camel_object_new(camel_imap_summary_get_type());
+ camel_folder_summary_set_build_content(s, TRUE);
+ camel_folder_summary_set_filename(s, path);
+ if (camel_folder_summary_header_load(s) != -1) {
+ fi->unread = s->unread_count;
+ fi->total = s->saved_count;
+ }
+ g_free(storage_path);
+ g_free(folder_dir);
+ g_free(path);
+
+ camel_object_unref(s);
+ }
+}
+
+/* NB: We should have connect_lock at this point */
+static void
+get_folder_counts(CamelImapStore *imap_store, CamelFolderInfo *fi, CamelException *ex)
+{
+ GSList *q;
+ CamelFolder *folder;
+
+ /* non-recursive breath first search */
+
+ q = g_slist_append(NULL, fi);
+
+ while (q) {
+ fi = q->data;
+ q = g_slist_remove_link(q, q);
+
+ while (fi) {
+ /* ignore noselect folders, and check only inbox if we only check inbox */
+ if ((fi->flags & CAMEL_FOLDER_NOSELECT) == 0
+ && ( (imap_store->parameters & IMAP_PARAM_CHECK_ALL)
+ || g_ascii_strcasecmp(fi->full_name, "inbox") == 0) ) {
+
+ /* For the current folder, poke it to check for new
+ * messages and then report that number, rather than
+ * doing a STATUS command.
+ */
+ if (imap_store->current_folder && strcmp(imap_store->current_folder->full_name, fi->full_name) == 0) {
+ /* we bypass the folder locking otherwise we can deadlock. we use the command lock for
+ any operations anyway so this is 'safe'. See comment above imap_store_refresh_folders() for info */
+ CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(imap_store->current_folder))->refresh_info(imap_store->current_folder, ex);
+ fi->unread = camel_folder_get_unread_message_count (imap_store->current_folder);
+ fi->total = camel_folder_get_message_count(imap_store->current_folder);
+ } else {
+ struct imap_status_item *items, *item;
+
+ fi->unread = -1;
+ fi->total = -1;
+
+ item = items = get_folder_status (imap_store, fi->full_name, "MESSAGES UNSEEN");
+ while (item != NULL) {
+ if (!g_ascii_strcasecmp (item->name, "MESSAGES")) {
+ fi->total = item->value;
+ } else if (!g_ascii_strcasecmp (item->name, "UNSEEN")) {
+ fi->unread = item->value;
+ }
+
+ item = item->next;
+ }
+
+ imap_status_item_free (items);
+
+ /* if we have this folder open, and the unread count has changed, update */
+ folder = camel_object_bag_peek(CAMEL_STORE(imap_store)->folders, fi->full_name);
+ if (folder) {
+ if (fi->unread != camel_folder_get_unread_message_count(folder)) {
+ CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->refresh_info(folder, ex);
+ fi->unread = camel_folder_get_unread_message_count(folder);
+ fi->total = camel_folder_get_message_count(folder);
+ }
+ camel_object_unref(folder);
+ }
+ }
+ } else {
+ /* since its cheap, get it if they're open/consult summary file */
+ fill_fi((CamelStore *)imap_store, fi, 0);
+ }
+
+ if (fi->child)
+ q = g_slist_append(q, fi->child);
+ fi = fi->next;
+ }
+ }
+}
+
+/* imap needs to treat inbox case insensitive */
+/* we'll assume the names are normalised already */
+static guint folder_hash(const void *ap)
+{
+ const char *a = ap;
+
+ if (strcasecmp(a, "INBOX") == 0)
+ a = "INBOX";
+
+ return g_str_hash(a);
+}
+
+static int folder_eq(const void *ap, const void *bp)
+{
+ const char *a = ap;
+ const char *b = bp;
+
+ if (strcasecmp(a, "INBOX") == 0)
+ a = "INBOX";
+ if (strcasecmp(b, "INBOX") == 0)
+ b = "INBOX";
+
+ return g_str_equal(a, b);
+}
+
+static GSList *
+get_folders_add_folders(GSList *p, int recurse, GHashTable *infos, GPtrArray *folders, GPtrArray *folders_out)
+{
+ CamelFolderInfo *oldfi, *fi;
+ int i;
+
+ /* This is a nasty mess, because some servers will return
+ broken results from LIST or LSUB if you use '%'. e.g. you
+ may get (many) duplicate names, and worse, names may have
+ conflicting flags. */
+ for (i=0; i<folders->len; i++) {
+ fi = folders->pdata[i];
+ oldfi = g_hash_table_lookup(infos, fi->full_name);
+ if (oldfi == NULL) {
+ d(printf(" new folder '%s'\n", fi->full_name));
+ g_hash_table_insert(infos, fi->full_name, fi);
+ if (recurse)
+ p = g_slist_prepend(p, fi);
+ g_ptr_array_add(folders_out, fi);
+ } else {
+ d(printf(" old folder '%s', old flags %08x new flags %08x\n", fi->full_name, oldfi->flags, fi->flags));
+
+ /* need to special-case noselect, since it also affects the uri */
+ if ((oldfi->flags & CAMEL_FOLDER_NOSELECT) != 0
+ && (fi->flags & CAMEL_FOLDER_NOSELECT) == 0) {
+ g_free(oldfi->uri);
+ oldfi->uri = fi->uri;
+ fi->uri = NULL;
+ }
+
+ /* some flags are anded together, some are or'd */
+
+ oldfi->flags = (oldfi->flags & fi->flags & (CAMEL_FOLDER_NOSELECT|CAMEL_FOLDER_NOINFERIORS))
+ | ((oldfi->flags | fi->flags) & ~(CAMEL_FOLDER_NOSELECT|CAMEL_FOLDER_NOINFERIORS));
+
+ camel_folder_info_free(fi);
+ }
+ }
+
+ g_ptr_array_set_size(folders, 0);
+
+ return p;
+}
+
+static GPtrArray *
+get_folders(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ GSList *q, *p = NULL;
+ GHashTable *infos;
+ int i;
+ GPtrArray *folders, *folders_out;
+ CamelFolderInfo *fi;
+ char *name;
+ int depth = 0;
+ int haveinbox = 0;
+ static int imap_max_depth = 0;
+
+ if (!camel_imap_store_connected (imap_store, ex))
+ return NULL;
+
+ if (camel_debug("imap:folder_info"))
+ printf(" get_folders\n");
+
+ /* allow megalomaniacs to override the max of 10 */
+ if (imap_max_depth == 0) {
+ name = getenv("CAMEL_IMAP_MAX_DEPTH");
+ if (name) {
+ imap_max_depth = atoi (name);
+ imap_max_depth = MIN (MAX (imap_max_depth, 0), 2);
+ } else
+ imap_max_depth = 10;
+ }
+
+ infos = g_hash_table_new(folder_hash, folder_eq);
+
+ /* get starting point & strip trailing '/' */
+ if (top[0] == 0) {
+ if (imap_store->namespace) {
+ top = imap_store->namespace;
+ i = strlen(top)-1;
+ name = g_malloc(i+2);
+ strcpy(name, top);
+ while (i>0 && name[i] == imap_store->dir_sep)
+ name[i--] = 0;
+ } else
+ name = g_strdup("");
+ } else {
+ name = camel_imap_store_summary_full_from_path(imap_store->summary, top);
+ if (name == NULL)
+ name = camel_imap_store_summary_path_to_full(imap_store->summary, top, imap_store->dir_sep);
+ }
+
+ d(printf("\n\nList '%s' %s\n", name, flags&CAMEL_STORE_FOLDER_INFO_RECURSIVE?"RECURSIVE":"NON-RECURSIVE"));
+
+ folders_out = g_ptr_array_new();
+ folders = g_ptr_array_new();
+
+ /* first get working list of names */
+ get_folders_online (imap_store, name[0]?name:"%", folders, flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex);
+ if (camel_exception_is_set(ex))
+ goto fail;
+ for (i=0; i<folders->len && !haveinbox; i++) {
+ fi = folders->pdata[i];
+ haveinbox = (strcasecmp(fi->full_name, "INBOX")) == 0;
+ }
+
+ if (!haveinbox && top == imap_store->namespace) {
+ get_folders_online (imap_store, "INBOX", folders,
+ flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex);
+
+ if (camel_exception_is_set (ex))
+ goto fail;
+ }
+
+ p = get_folders_add_folders(p, TRUE, infos, folders, folders_out);
+
+ /* p is a reversed list of pending folders for the next level, q is the list of folders for this */
+ while (p) {
+ q = g_slist_reverse(p);
+
+ p = NULL;
+ while (q) {
+ fi = q->data;
+
+ q = g_slist_remove_link(q, q);
+
+ d(printf("Checking parent folder '%s'\n", fi->full_name));
+
+ /* First if we're not recursive mode on the top level, and we know it has or doesn't
+ or can't have children, no need to go further - a bit ugly */
+ if ( top == imap_store->namespace
+ && (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) == 0
+ && (fi->flags & (CAMEL_FOLDER_CHILDREN|CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS)) != 0) {
+ /* do nothing */
+ d(printf(" not interested in folder right now ...\n"));
+ }
+ /* Otherwise, if this has (or might have) children, scan it */
+ else if ( (fi->flags & (CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS)) == 0
+ || (fi->flags & CAMEL_FOLDER_CHILDREN) != 0) {
+ char *n, *real;
+
+ real = camel_imap_store_summary_full_from_path(imap_store->summary, fi->full_name);
+ n = imap_concat(imap_store, real?real:fi->full_name, "%");
+ get_folders_online(imap_store, n, folders, flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex);
+ g_free(n);
+ g_free(real);
+
+ if (camel_exception_is_set (ex))
+ goto fail;
+
+ if (folders->len > 0)
+ fi->flags |= CAMEL_FOLDER_CHILDREN;
+
+ p = get_folders_add_folders(p, (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) && depth<imap_max_depth,
+ infos, folders, folders_out);
+ }
+ }
+ depth++;
+ }
+
+ g_ptr_array_free(folders, TRUE);
+ g_hash_table_destroy(infos);
+ g_free(name);
+
+ return folders_out;
+fail:
+ g_ptr_array_free(folders, TRUE);
+ g_ptr_array_free(folders_out, TRUE);
+ g_hash_table_destroy(infos);
+ g_slist_free (p);
+ g_free(name);
+
+ return NULL;
+}
+
+static CamelFolderInfo *
+get_folder_info_online (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelFolderInfo *tree = NULL;
+ GPtrArray *folders;
+
+ if (top == NULL)
+ top = "";
+
+ if (camel_debug("imap:folder_info"))
+ printf("get folder info online\n");
+
+ CAMEL_SERVICE_LOCK(store, connect_lock);
+
+ if ((flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)
+ && !(imap_store->capabilities & IMAP_CAPABILITY_useful_lsub)
+ && (imap_store->parameters & IMAP_PARAM_CHECK_ALL))
+ folders = get_subscribed_folders(imap_store, top, ex);
+ else
+ folders = get_folders(store, top, flags, ex);
+
+ if (folders == NULL)
+ goto done;
+
+ tree = camel_folder_info_build(folders, top, '/', TRUE);
+ g_ptr_array_free(folders, TRUE);
+
+ if (!(flags & CAMEL_STORE_FOLDER_INFO_FAST))
+ get_folder_counts(imap_store, tree, ex);
+
+ d(dumpfi(tree));
+ camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
+done:
+ CAMEL_SERVICE_UNLOCK(store, connect_lock);
+
+ return tree;
+}
+
+static gboolean
+get_one_folder_offline (const char *physical_path, const char *path, gpointer data)
+{
+ GPtrArray *folders = data;
+ CamelImapStore *imap_store = folders->pdata[0];
+ CamelFolderInfo *fi;
+ CamelStoreInfo *si;
+
+ if (*path != '/')
+ return TRUE;
+
+ /* folder_info_build will insert parent nodes as necessary and mark
+ * them as noselect, which is information we actually don't have at
+ * the moment. So let it do the right thing by bailing out if it's
+ * not a folder we're explicitly interested in.
+ */
+
+ si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, path+1);
+ if (si) {
+ if ((((CamelStore *)imap_store)->flags & CAMEL_STORE_SUBSCRIPTIONS) == 0
+ || (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) {
+ fi = imap_build_folder_info(imap_store, path+1);
+ fi->flags = si->flags;
+ /* HACK: some servers report noinferiors for all folders (uw-imapd)
+ We just translate this into nochildren, and let the imap layer enforce
+ it. See create folder */
+ if (fi->flags & CAMEL_FOLDER_NOINFERIORS)
+ fi->flags = (fi->flags & ~CAMEL_FOLDER_NOINFERIORS) | CAMEL_FOLDER_NOCHILDREN;
+
+ if (si->flags & CAMEL_FOLDER_NOSELECT) {
+ CamelURL *url = camel_url_new(fi->uri, NULL);
+
+ camel_url_set_param (url, "noselect", "yes");
+ g_free(fi->uri);
+ fi->uri = camel_url_to_string (url, 0);
+ camel_url_free (url);
+ } else {
+ fill_fi((CamelStore *)imap_store, fi, 0);
+ }
+ g_ptr_array_add (folders, fi);
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+
+ return TRUE;
+}
+
+static CamelFolderInfo *
+get_folder_info_offline (CamelStore *store, const char *top,
+ guint32 flags, CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelFolderInfo *fi;
+ GPtrArray *folders;
+ char *storage_path;
+
+ if (camel_debug("imap:folder_info"))
+ printf("get folder info offline\n");
+
+ if (!imap_store->connected &&
+ !camel_service_connect (CAMEL_SERVICE (store), ex))
+ return NULL;
+
+ if ((store->flags & CAMEL_STORE_SUBSCRIPTIONS) &&
+ !(flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)) {
+ camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex);
+ return NULL;
+ }
+
+ /* FIXME: obey other flags */
+
+ folders = g_ptr_array_new ();
+
+ /* A kludge to avoid having to pass a struct to the callback */
+ g_ptr_array_add (folders, imap_store);
+ storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
+ if (!e_path_find_folders (storage_path, get_one_folder_offline, folders)) {
+ camel_disco_store_check_online (CAMEL_DISCO_STORE (imap_store), ex);
+ fi = NULL;
+ } else {
+ g_ptr_array_remove_index_fast (folders, 0);
+ fi = camel_folder_info_build (folders, "", '/', TRUE);
+ }
+ g_free(storage_path);
+
+ g_ptr_array_free (folders, TRUE);
+ return fi;
+}
+
+static gboolean
+folder_subscribed (CamelStore *store, const char *folder_name)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelStoreInfo *si;
+ int truth = FALSE;
+
+ si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
+ if (si) {
+ truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+
+ return truth;
+}
+
+/* Note: folder_name must match a folder as listed with get_folder_info() -> full_name */
+static void
+subscribe_folder (CamelStore *store, const char *folder_name,
+ CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelImapResponse *response;
+ CamelFolderInfo *fi;
+ CamelStoreInfo *si;
+
+ if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
+ return;
+ if (!camel_imap_store_connected (imap_store, ex))
+ return;
+
+ response = camel_imap_command (imap_store, NULL, ex,
+ "SUBSCRIBE %F", folder_name);
+ if (!response)
+ return;
+ camel_imap_response_free (imap_store, response);
+
+ si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
+ if (si) {
+ if ((si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) {
+ si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
+ camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
+ camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
+ }
+ camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
+ }
+
+ if (imap_store->renaming) {
+ /* we don't need to emit a "folder_subscribed" signal
+ if we are in the process of renaming folders, so we
+ are done here... */
+ return;
+ }
+
+ fi = imap_build_folder_info(imap_store, folder_name);
+ fi->flags |= CAMEL_FOLDER_NOCHILDREN;
+
+ camel_object_trigger_event (CAMEL_OBJECT (store), "folder_subscribed", fi);
+ camel_folder_info_free (fi);
+}
+
+static void
+unsubscribe_folder (CamelStore *store, const char *folder_name,
+ CamelException *ex)
+{
+ CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
+ CamelImapResponse *response;
+
+ if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
+ return;
+ if (!camel_imap_store_connected (imap_store, ex))
+ return;
+
+ response = camel_imap_command (imap_store, NULL, ex,
+ "UNSUBSCRIBE %F", folder_name);
+ if (!response)
+ return;
+ camel_imap_response_free (imap_store, response);
+
+ imap_folder_effectively_unsubscribed (imap_store, folder_name, ex);
+}
+
+#if 0
+static gboolean
+folder_flags_have_changed (CamelFolder *folder)
+{
+ CamelMessageInfo *info;
+ int i, max;
+
+ max = camel_folder_summary_count (folder->summary);
+ for (i = 0; i < max; i++) {
+ info = camel_folder_summary_index (folder->summary, i);
+ if (!info)
+ continue;
+ if (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+#endif
+
+
+gboolean
+camel_imap_store_connected (CamelImapStore *store, CamelException *ex)
+{
+ if (store->istream == NULL || !store->connected)
+ return camel_service_connect (CAMEL_SERVICE (store), ex);
+ return TRUE;
+}
+
+
+/* FIXME: please god, when will the hurting stop? Thus function is so
+ fucking broken it's not even funny. */
+ssize_t
+camel_imap_store_readline (CamelImapStore *store, char **dest, CamelException *ex)
+{
+ CamelStreamBuffer *stream;
+ char linebuf[1024];
+ GByteArray *ba;
+ ssize_t nread;
+
+ g_return_val_if_fail (CAMEL_IS_IMAP_STORE (store), -1);
+ g_return_val_if_fail (dest, -1);
+
+ *dest = NULL;
+
+ /* Check for connectedness. Failed (or cancelled) operations will
+ * close the connection. We can't expect a read to have any
+ * meaning if we reconnect, so always set an exception.
+ */
+
+ if (!camel_imap_store_connected (store, ex))
+ return -1;
+
+ stream = CAMEL_STREAM_BUFFER (store->istream);
+
+ ba = g_byte_array_new ();
+ while ((nread = camel_stream_buffer_gets (stream, linebuf, sizeof (linebuf))) > 0) {
+ g_byte_array_append (ba, linebuf, nread);
+ if (linebuf[nread - 1] == '\n')
+ break;
+ }
+
+ if (nread <= 0) {
+ if (errno == EINTR)
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Operation cancelled"));
+ else
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Server unexpectedly disconnected: %s"),
+ g_strerror (errno));
+
+ camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
+ g_byte_array_free (ba, TRUE);
+ return -1;
+ }
+
+ if (camel_verbose_debug) {
+ fprintf (stderr, "received: ");
+ fwrite (ba->data, 1, ba->len, stderr);
+ }
+
+ /* camel-imap-command.c:imap_read_untagged expects the CRLFs
+ to be stripped off and be nul-terminated *sigh* */
+ nread = ba->len - 1;
+ ba->data[nread] = '\0';
+ if (ba->data[nread - 1] == '\r') {
+ ba->data[nread - 1] = '\0';
+ nread--;
+ }
+
+ *dest = ba->data;
+ g_byte_array_free (ba, FALSE);
+
+ return nread;
+}
diff --git a/camel/providers/imapp/camel-imapp-store.c b/camel/providers/imapp/camel-imapp-store.c
index 290d3c0a1..2176c91cc 100644
--- a/camel/providers/imapp/camel-imapp-store.c
+++ b/camel/providers/imapp/camel-imapp-store.c
@@ -189,29 +189,32 @@ connect_to_server (CamelService *service, int ssl_mode, int try_starttls)
CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service);
CamelStream * volatile tcp_stream = NULL;
CamelIMAPPStream * volatile imap_stream = NULL;
- struct hostent *h = NULL;
- int ret, port;
+ int ret;
CamelException *ex;
ex = camel_exception_new();
CAMEL_TRY {
+ char *serv;
+ struct addrinfo *ai, hints = { 0 };
+
/* parent class connect initialization */
CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex);
if (ex->id)
camel_exception_throw_ex(ex);
- h = camel_service_gethost(service, ex);
- if (ex->id)
- camel_exception_throw_ex(ex);
-
- port = service->url->port ? service->url->port : IMAP_PORT;
-
+ if (service->url->port) {
+ serv = g_alloca(16);
+ sprintf(serv, "%d", service->url->port);
+ } else
+ serv = "imap";
+
#ifdef HAVE_SSL
if (camel_url_get_param (service->url, "use_ssl")) {
if (try_starttls)
tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
else {
- port = service->url->port ? service->url->port : 995;
+ if (service->url->port == 0)
+ serv = "imaps";
tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
}
} else {
@@ -220,16 +223,21 @@ connect_to_server (CamelService *service, int ssl_mode, int try_starttls)
#else
tcp_stream = camel_tcp_stream_raw_new ();
#endif /* HAVE_SSL */
-
- ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port);
- camel_free_host (h);
+
+ hints.ai_socktype = SOCK_STREAM;
+ ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
+ if (ex->id)
+ camel_exception_throw_ex(ex);
+
+ ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai);
+ camel_freeaddrinfo(ai);
if (ret == -1) {
if (errno == EINTR)
camel_exception_throw(CAMEL_EXCEPTION_USER_CANCEL, _("Connection cancelled"));
else
camel_exception_throw(CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
- _("Could not connect to %s (port %d): %s"),
- service->url->host, port, strerror(errno));
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv, strerror(errno));
}
imap_stream = (CamelIMAPPStream *)camel_imapp_stream_new(tcp_stream);
diff --git a/camel/providers/nntp/camel-nntp-store.c b/camel/providers/nntp/camel-nntp-store.c
index 31048e7a1..c03fe4a7d 100644
--- a/camel/providers/nntp/camel-nntp-store.c
+++ b/camel/providers/nntp/camel-nntp-store.c
@@ -162,9 +162,10 @@ connect_to_server (CamelService *service, int ssl_mode, CamelException *ex)
gboolean retval = FALSE;
unsigned char *buf;
unsigned int len;
- struct hostent *h;
- int port, ret;
+ int ret;
char *path;
+ struct addrinfo *ai, hints = { 0 };
+ char *serv;
CAMEL_NNTP_STORE_LOCK(store, command_lock);
@@ -181,15 +182,17 @@ connect_to_server (CamelService *service, int ssl_mode, CamelException *ex)
camel_data_cache_set_expire_age (store->cache, 60*60*24*14);
camel_data_cache_set_expire_access (store->cache, 60*60*24*5);
}
-
- if (!(h = camel_service_gethost (service, ex)))
- goto fail;
-
- port = service->url->port ? service->url->port : NNTP_PORT;
+
+ if (service->url->port) {
+ serv = g_alloca(16);
+ sprintf(serv, "%d", service->url->port);
+ } else
+ serv = "nntp";
#ifdef HAVE_SSL
if (ssl_mode != USE_SSL_NEVER) {
- port = service->url->port ? service->url->port : NNTPS_PORT;
+ if (service->url->port == 0)
+ serv = "nntps";
tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3);
} else {
tcp_stream = camel_tcp_stream_raw_new ();
@@ -197,17 +200,24 @@ connect_to_server (CamelService *service, int ssl_mode, CamelException *ex)
#else
tcp_stream = camel_tcp_stream_raw_new ();
#endif /* HAVE_SSL */
+
+ hints.ai_socktype = SOCK_STREAM;
+ ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
+ if (ai == NULL) {
+ camel_object_unref(tcp_stream);
+ goto fail;
+ }
- ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port);
- camel_free_host (h);
+ ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai);
+ camel_freeaddrinfo(ai);
if (ret == -1) {
if (errno == EINTR)
camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
_("Connection cancelled"));
else
camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
- _("Could not connect to %s (port %d): %s"),
- service->url->host, port, g_strerror (errno));
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv, g_strerror (errno));
camel_object_unref (tcp_stream);
diff --git a/camel/providers/pop3/camel-pop3-store.c b/camel/providers/pop3/camel-pop3-store.c
index 96f9295b5..f2d903b1c 100644
--- a/camel/providers/pop3/camel-pop3-store.c
+++ b/camel/providers/pop3/camel-pop3-store.c
@@ -148,52 +148,59 @@ connect_to_server (CamelService *service, int ssl_mode, int try_starttls, CamelE
CamelPOP3Store *store = CAMEL_POP3_STORE (service);
CamelStream *tcp_stream;
CamelPOP3Command *pc;
- struct hostent *h;
guint32 flags = 0;
int clean_quit;
- int ret, port;
-
- h = camel_service_gethost (service, ex);
- if (!h)
- return FALSE;
-
- port = service->url->port ? service->url->port : 110;
-
+ int ret;
+ struct addrinfo *ai, hints = { 0 };
+ char *serv;
+
+ if (service->url->port) {
+ serv = g_alloca(16);
+ sprintf(serv, "%d", service->url->port);
+ } else
+ serv = "pop3";
+
if (ssl_mode != USE_SSL_NEVER) {
#ifdef HAVE_SSL
if (try_starttls) {
tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
} else {
- port = service->url->port ? service->url->port : 995;
+ if (service->url->port == 0)
+ serv = "pop3s";
tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
}
#else
- if (!try_starttls)
- port = service->url->port ? service->url->port : 995;
+ if (!try_starttls && service->url->port == 0)
+ serv = "pop3s";
camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
- _("Could not connect to %s (port %d): %s"),
- service->url->host, port,
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv,
_("SSL unavailable"));
- camel_free_host (h);
-
return FALSE;
#endif /* HAVE_SSL */
} else {
tcp_stream = camel_tcp_stream_raw_new ();
}
+
+ hints.ai_socktype = SOCK_STREAM;
+ ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
+ if (ai == NULL) {
+ camel_object_unref(tcp_stream);
+ return FALSE;
+ }
- ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port);
- camel_free_host (h);
+ ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai);
+ camel_freeaddrinfo(ai);
if (ret == -1) {
if (errno == EINTR)
camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
_("Connection cancelled"));
else
camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
- _("Could not connect to POP server %s (port %d): %s"),
- service->url->host, port, g_strerror (errno));
+ _("Could not connect to POP server %s (port %s): %s"),
+ service->url->host, serv, g_strerror (errno));
camel_object_unref (tcp_stream);
@@ -211,8 +218,8 @@ connect_to_server (CamelService *service, int ssl_mode, int try_starttls, CamelE
if (!(store->engine = camel_pop3_engine_new (tcp_stream, flags))) {
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
- _("Failed to read a valid greeting from POP server %s (port %d)"),
- service->url->host, port);
+ _("Failed to read a valid greeting from POP server %s (port %s)"),
+ service->url->host, serv);
return FALSE;
}
diff --git a/camel/providers/smtp/camel-smtp-transport.c b/camel/providers/smtp/camel-smtp-transport.c
index 3245cc68a..fc313ed4d 100644
--- a/camel/providers/smtp/camel-smtp-transport.c
+++ b/camel/providers/smtp/camel-smtp-transport.c
@@ -237,53 +237,60 @@ connect_to_server (CamelService *service, int try_starttls, CamelException *ex)
CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
CamelStream *tcp_stream;
char *respbuf = NULL;
- struct hostent *h;
- int port, ret;
+ int ret;
+ struct addrinfo *ai, hints = { 0 };
+ char *serv;
if (!CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex))
return FALSE;
- h = camel_service_gethost (service, ex);
- if (!h)
- return FALSE;
-
/* set some smtp transport defaults */
transport->flags &= CAMEL_SMTP_TRANSPORT_USE_SSL; /* reset all but ssl flags */
transport->authtypes = NULL;
-
- port = service->url->port ? service->url->port : SMTP_PORT;
+
+ if (service->url->port) {
+ serv = g_alloca(16);
+ sprintf(serv, "%d", service->url->port);
+ } else
+ serv = "smtp";
if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL) {
#ifdef HAVE_SSL
if (try_starttls) {
tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
} else {
- port = service->url->port ? service->url->port : 465;
+ if (service->url->port == 0)
+ serv = "smtps";
tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
}
#else
- if (!try_starttls)
- port = service->url->port ? service->url->port : 465;
+ if (!try_starttls && service->url->port == 0)
+ serv = "smtps";
camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
- _("Could not connect to %s (port %d): %s"),
- service->url->host, port,
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv,
_("SSL unavailable"));
-
- camel_free_host (h);
-
+
return FALSE;
#endif /* HAVE_SSL */
} else {
tcp_stream = camel_tcp_stream_raw_new ();
}
+
+ hints.ai_socktype = SOCK_STREAM;
+ ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
+ if (ai == NULL) {
+ camel_object_unref(tcp_stream);
+ return FALSE;
+ }
- ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port);
- camel_free_host (h);
+ ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai);
+ camel_freeaddrinfo(ai);
if (ret == -1) {
camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
- _("Could not connect to %s (port %d): %s"),
- service->url->host, port,
+ _("Could not connect to %s (port %s): %s"),
+ service->url->host, serv,
g_strerror (errno));
camel_object_unref (tcp_stream);
@@ -294,7 +301,7 @@ connect_to_server (CamelService *service, int try_starttls, CamelException *ex)
transport->connected = TRUE;
/* get the localaddr - needed later by smtp_helo */
- transport->localaddr = camel_tcp_stream_get_local_address (CAMEL_TCP_STREAM (tcp_stream));
+ transport->localaddr = camel_tcp_stream_get_local_address (CAMEL_TCP_STREAM (tcp_stream), &transport->localaddrlen);
transport->ostream = tcp_stream;
transport->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
@@ -580,7 +587,7 @@ smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex)
transport->ostream = NULL;
}
- camel_tcp_address_free (transport->localaddr);
+ g_free(transport->localaddr);
transport->localaddr = NULL;
transport->connected = FALSE;
@@ -865,10 +872,7 @@ smtp_helo (CamelSmtpTransport *transport, CamelException *ex)
{
/* say hello to the server */
char *name = NULL, *cmdbuf = NULL, *respbuf = NULL;
- struct hostent *host;
- CamelException err;
const char *token;
- int af;
/* these are flags that we set, so unset them in case we
are being called a second time (ie, after a STARTTLS) */
@@ -883,42 +887,10 @@ smtp_helo (CamelSmtpTransport *transport, CamelException *ex)
}
camel_operation_start_transient (NULL, _("SMTP Greeting"));
-
- /* get the local host name */
- camel_exception_init (&err);
-#ifdef ENABLE_IPv6
- af = transport->localaddr->family == CAMEL_TCP_ADDRESS_IPv6 ? AF_INET6 : AF_INET;
-#else
- af = AF_INET;
-#endif
- host = camel_gethostbyaddr ((char *) &transport->localaddr->address,
- transport->localaddr->length, af, &err);
-
- camel_exception_clear (&err);
-
- if (host && host->h_name && *host->h_name) {
- name = g_strdup (host->h_name);
- } else {
-#ifdef ENABLE_IPv6
- char ip[MAXHOSTNAMELEN + 1];
- const char *proto;
-
- proto = transport->localaddr->family == CAMEL_TCP_ADDRESS_IPv6 ? "IPv6:" : "";
- name = g_strdup_printf ("[%s%s]", proto, inet_ntop (af, transport->localaddr->address, ip, MAXHOSTNAMELEN));
-#else
- /* We *could* use inet_ntoa() here, but it's probably
- not worth it since we would have to worry about
- some systems not having inet_ntoa() */
- name = g_strdup_printf ("[%d.%d.%d.%d]",
- transport->localaddr->address[0],
- transport->localaddr->address[1],
- transport->localaddr->address[2],
- transport->localaddr->address[3]);
-#endif
- }
-
- if (host)
- camel_free_host (host);
+
+ /* this can't really fail with the flags we're using, it should fallback to numerical */
+ if (camel_getnameinfo(transport->localaddr, transport->localaddrlen, &name, NULL, 0, NULL) != 0)
+ name = g_strdup("localhost.localdomain");
/* hiya server! how are you today? */
if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP)
diff --git a/camel/providers/smtp/camel-smtp-transport.h b/camel/providers/smtp/camel-smtp-transport.h
index ef15f2b07..87fcafb58 100644
--- a/camel/providers/smtp/camel-smtp-transport.h
+++ b/camel/providers/smtp/camel-smtp-transport.h
@@ -22,17 +22,14 @@
* USA
*/
-
#ifndef CAMEL_SMTP_TRANSPORT_H
#define CAMEL_SMTP_TRANSPORT_H 1
-
#ifdef __cplusplus
extern "C" {
#pragma }
#endif /* __cplusplus */
-
#include "camel-transport.h"
#include "camel-tcp-stream.h"
@@ -41,7 +38,6 @@ extern "C" {
#define CAMEL_SMTP_TRANSPORT_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_SMTP_TRANSPORT_TYPE, CamelSmtpTransportClass))
#define CAMEL_IS_SMTP_TRANSPORT(o) (CAMEL_CHECK_TYPE((o), CAMEL_SMTP_TRANSPORT_TYPE))
-
#define CAMEL_SMTP_TRANSPORT_IS_ESMTP (1 << 0)
#define CAMEL_SMTP_TRANSPORT_8BITMIME (1 << 1)
#define CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES (1 << 2)
@@ -63,20 +59,17 @@ typedef struct {
guint32 flags;
gboolean connected;
- CamelTcpAddress *localaddr;
+ struct sockaddr *localaddr;
+ socklen_t localaddrlen;
GHashTable *authtypes;
-
} CamelSmtpTransport;
-
-
typedef struct {
CamelTransportClass parent_class;
} CamelSmtpTransportClass;
-
/* Standard Camel function */
CamelType camel_smtp_transport_get_type (void);
@@ -85,5 +78,3 @@ CamelType camel_smtp_transport_get_type (void);
#endif /* __cplusplus */
#endif /* CAMEL_SMTP_TRANSPORT_H */
-
-