1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2000-2003 Ximian Inc.
5 * Authors: Michael Zucchi <notzed@ximian.com>
6 * Jeffrey Stedfast <fejj@ximian.com>
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of version 2 of the GNU Lesser General Public
10 * License as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this program; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
27 /* POSIX requires <sys/types.h> be included before <regex.h> */
28 #include <sys/types.h>
40 #include <sys/param.h> /* for MAXHOSTNAMELEN */
43 #ifndef MAXHOSTNAMELEN
44 #define MAXHOSTNAMELEN 1024
49 #include <libedataserver/e-iconv.h>
50 #include <libedataserver/e-time-utils.h>
52 #include "camel-charset-map.h"
53 #include "camel-mime-utils.h"
54 #include "camel-net-utils.h"
55 #include "camel-utf8.h"
58 #include "broken-date-parser.h"
62 /* Undef the similar macro from pthread.h, it doesn't check if
63 * gmtime() returns NULL.
67 /* The gmtime() in Microsoft's C library is MT-safe */
68 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
76 #define g_strdup(x) (strdup_count++, g_strdup(x))
77 #define g_malloc(x) (malloc_count++, g_malloc(x))
78 #define g_free(x) (free_count++, g_free(x))
81 /* for all non-essential warnings ... */
87 #define CAMEL_UUENCODE_CHAR(c) ((c) ? (c) + ' ' : '`')
88 #define CAMEL_UUDECODE_CHAR(c) (((c) - ' ') & 077)
90 static const char base64_alphabet[] =
91 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
93 static const unsigned char tohex[16] = {
94 '0', '1', '2', '3', '4', '5', '6', '7',
95 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
98 static const unsigned char camel_mime_base64_rank[256] = {
99 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
100 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
101 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
102 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
103 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
104 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
105 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
106 0x3c, 0x3d, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
107 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
108 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
109 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
110 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
111 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
112 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
113 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
114 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
115 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
116 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
117 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
118 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
119 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
120 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
121 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
122 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
123 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
124 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
125 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
126 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
127 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
128 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
129 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
130 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
135 * camel_base64_encode_close:
137 * @inlen: length of the input
138 * @break_lines: whether or not to break long lines
139 * @out: output string
140 * @state: holds the number of bits that are stored in @save
141 * @save: leftover bits that have not yet been encoded
143 * Base64 encodes the input stream to the output stream. Call this
144 * when finished encoding data with #camel_base64_encode_step
145 * to flush off the last little bit.
147 * Returns the number of bytes encoded
150 camel_base64_encode_close(unsigned char *in, size_t inlen, gboolean break_lines, unsigned char *out, int *state, int *save)
153 unsigned char *outptr = out;
156 outptr += camel_base64_encode_step(in, inlen, break_lines, outptr, state, save);
158 c1 = ((unsigned char *)save)[1];
159 c2 = ((unsigned char *)save)[2];
161 d(printf("mode = %d\nc1 = %c\nc2 = %c\n",
162 (int)((char *)save)[0],
163 (int)((char *)save)[1],
164 (int)((char *)save)[2]));
166 switch (((char *)save)[0]) {
168 outptr[2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ];
169 g_assert(outptr[2] != 0);
174 outptr[0] = base64_alphabet[ c1 >> 2 ];
175 outptr[1] = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 )];
191 * camel_base64_encode_step:
193 * @inlen: length of the input
194 * @break_lines: break long lines
195 * @out: output string
196 * @state: holds the number of bits that are stored in @save
197 * @save: leftover bits that have not yet been encoded
199 * Base64 encodes a chunk of data. Performs an 'encode step', only
200 * encodes blocks of 3 characters to the output at a time, saves
201 * left-over state in state and save (initialise to 0 on first
204 * Returns the number of bytes encoded
207 camel_base64_encode_step(unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save)
209 register unsigned char *inptr, *outptr;
217 d(printf("we have %d chars, and %d saved chars\n", len, ((char *)save)[0]));
219 if (len + ((char *)save)[0] > 2) {
220 unsigned char *inend = in+len-2;
221 register int c1, c2, c3;
222 register int already;
226 switch (((char *)save)[0]) {
227 case 1: c1 = ((unsigned char *)save)[1]; goto skip1;
228 case 2: c1 = ((unsigned char *)save)[1];
229 c2 = ((unsigned char *)save)[2]; goto skip2;
232 /* yes, we jump into the loop, no i'm not going to change it, it's beautiful! */
233 while (inptr < inend) {
239 *outptr++ = base64_alphabet[ c1 >> 2 ];
240 *outptr++ = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 ) ];
241 *outptr++ = base64_alphabet[ ( (c2 &0x0f) << 2 ) | (c3 >> 6) ];
242 *outptr++ = base64_alphabet[ c3 & 0x3f ];
243 /* this is a bit ugly ... */
244 if (break_lines && (++already)>=19) {
250 ((char *)save)[0] = 0;
251 len = 2-(inptr-inend);
255 d(printf("state = %d, len = %d\n",
256 (int)((char *)save)[0],
260 register char *saveout;
262 /* points to the slot for the next char to save */
263 saveout = & (((char *)save)[1]) + ((char *)save)[0];
265 /* len can only be 0 1 or 2 */
267 case 2: *saveout++ = *inptr++;
268 case 1: *saveout++ = *inptr++;
270 ((char *)save)[0]+=len;
273 d(printf("mode = %d\nc1 = %c\nc2 = %c\n",
274 (int)((char *)save)[0],
275 (int)((char *)save)[1],
276 (int)((char *)save)[2]));
283 * camel_base64_decode_step: decode a chunk of base64 encoded data
285 * @len: max length of data to decode
286 * @out: output stream
287 * @state: holds the number of bits that are stored in @save
288 * @save: leftover bits that have not yet been decoded
290 * Decodes a chunk of base64 encoded data
292 * Returns the number of bytes decoded (which have been dumped in @out)
295 camel_base64_decode_step(unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save)
297 register unsigned char *inptr, *outptr;
298 unsigned char *inend, c;
299 register unsigned int v;
305 /* convert 4 base64 bytes to 3 normal bytes */
309 while (inptr<inend) {
310 c = camel_mime_base64_rank[*inptr++];
326 /* quick scan back for '=' on the end somewhere */
327 /* fortunately we can drop 1 output char for each trailing = (upto 2) */
329 while (inptr>in && i) {
331 if (camel_mime_base64_rank[*inptr] != 0xff) {
332 if (*inptr == '=' && outptr>out)
338 /* if i!= 0 then there is a truncation error! */
344 * camel_base64_encode_simple:
345 * @data: binary stream of data to encode
346 * @len: length of data
348 * Base64 encodes a block of memory.
350 * Returns a string containing the base64 encoded data
353 camel_base64_encode_simple (const char *data, size_t len)
356 int state = 0, outlen;
357 unsigned int save = 0;
359 out = g_malloc (len * 4 / 3 + 5);
360 outlen = camel_base64_encode_close ((unsigned char *)data, len, FALSE,
368 * camel_base64_decode_simple:
369 * @data: data to decode
370 * @len: length of data
372 * Base64 decodes @data inline (overwrites @data with the decoded version).
374 * Returns the new length of @data
377 camel_base64_decode_simple (char *data, size_t len)
380 unsigned int save = 0;
382 return camel_base64_decode_step ((unsigned char *)data, len,
383 (unsigned char *)data, &state, &save);
387 * camel_uuencode_close:
389 * @len: input stream length
390 * @out: output stream
391 * @uubuf: temporary buffer of 60 bytes
392 * @state: holds the number of bits that are stored in @save
393 * @save: leftover bits that have not yet been encoded
395 * Uuencodes a chunk of data. Call this when finished encoding data
396 * with #camel_uuencode_step to flush off the last little bit.
398 * Returns the number of bytes encoded
401 camel_uuencode_close (unsigned char *in, size_t len, unsigned char *out, unsigned char *uubuf, int *state, guint32 *save)
403 register unsigned char *outptr, *bufptr;
404 register guint32 saved;
405 int uulen, uufill, i;
410 outptr += camel_uuencode_step (in, len, out, uubuf, state, save);
416 uulen = (*state >> 8) & 0xff;
418 bufptr = uubuf + ((uulen / 3) * 4);
428 /* convert 3 normal bytes into 4 uuencoded bytes */
429 unsigned char b0, b1, b2;
432 b1 = saved >> 8 & 0xff;
435 *bufptr++ = CAMEL_UUENCODE_CHAR ((b0 >> 2) & 0x3f);
436 *bufptr++ = CAMEL_UUENCODE_CHAR (((b0 << 4) | ((b1 >> 4) & 0xf)) & 0x3f);
437 *bufptr++ = CAMEL_UUENCODE_CHAR (((b1 << 2) | ((b2 >> 6) & 0x3)) & 0x3f);
438 *bufptr++ = CAMEL_UUENCODE_CHAR (b2 & 0x3f);
447 int cplen = ((uulen / 3) * 4);
449 *outptr++ = CAMEL_UUENCODE_CHAR ((uulen - uufill) & 0xff);
450 memcpy (outptr, uubuf, cplen);
456 *outptr++ = CAMEL_UUENCODE_CHAR (uulen & 0xff);
467 * camel_uuencode_step:
469 * @len: input stream length
470 * @out: output stream
471 * @uubuf: temporary buffer of 60 bytes
472 * @state: holds the number of bits that are stored in @save
473 * @save: leftover bits that have not yet been encoded
475 * Uuencodes a chunk of data. Performs an 'encode step', only encodes
476 * blocks of 45 characters to the output at a time, saves left-over
477 * state in @uubuf, @state and @save (initialize to 0 on first
480 * Returns the number of bytes encoded
483 camel_uuencode_step (unsigned char *in, size_t len, unsigned char *out, unsigned char *uubuf, int *state, guint32 *save)
485 register unsigned char *inptr, *outptr, *bufptr;
486 unsigned char *inend;
487 register guint32 saved;
492 uulen = (*state >> 8) & 0xff;
499 bufptr = uubuf + ((uulen / 3) * 4);
501 while (inptr < inend) {
502 while (uulen < 45 && inptr < inend) {
503 while (i < 3 && inptr < inend) {
504 saved = (saved << 8) | *inptr++;
509 /* convert 3 normal bytes into 4 uuencoded bytes */
510 unsigned char b0, b1, b2;
513 b1 = saved >> 8 & 0xff;
516 *bufptr++ = CAMEL_UUENCODE_CHAR ((b0 >> 2) & 0x3f);
517 *bufptr++ = CAMEL_UUENCODE_CHAR (((b0 << 4) | ((b1 >> 4) & 0xf)) & 0x3f);
518 *bufptr++ = CAMEL_UUENCODE_CHAR (((b1 << 2) | ((b2 >> 6) & 0x3)) & 0x3f);
519 *bufptr++ = CAMEL_UUENCODE_CHAR (b2 & 0x3f);
528 *outptr++ = CAMEL_UUENCODE_CHAR (uulen & 0xff);
529 memcpy (outptr, uubuf, ((uulen / 3) * 4));
530 outptr += ((uulen / 3) * 4);
538 *state = ((uulen & 0xff) << 8) | (i & 0xff);
545 * camel_uudecode_step:
547 * @inlen: max length of data to decode
548 * @out: output stream
549 * @state: holds the number of bits that are stored in @save
550 * @save: leftover bits that have not yet been decoded
552 * Uudecodes a chunk of data. Performs a 'decode step' on a chunk of
553 * uuencoded data. Assumes the "begin mode filename" line has
556 * Returns the number of bytes decoded
559 camel_uudecode_step (unsigned char *in, size_t len, unsigned char *out, int *state, guint32 *save)
561 register unsigned char *inptr, *outptr;
562 unsigned char *inend, ch;
563 register guint32 saved;
564 gboolean last_was_eoln;
567 if (*state & CAMEL_UUDECODE_STATE_END)
572 uulen = (*state >> 8) & 0xff;
574 last_was_eoln = TRUE;
576 last_was_eoln = FALSE;
582 while (inptr < inend) {
583 if (*inptr == '\n' || last_was_eoln) {
584 if (last_was_eoln && *inptr != '\n') {
585 uulen = CAMEL_UUDECODE_CHAR (*inptr);
586 last_was_eoln = FALSE;
588 *state |= CAMEL_UUDECODE_STATE_END;
592 last_was_eoln = TRUE;
603 saved = (saved << 8) | ch;
606 /* convert 4 uuencoded bytes to 3 normal bytes */
607 unsigned char b0, b1, b2, b3;
610 b1 = saved >> 16 & 0xff;
611 b2 = saved >> 8 & 0xff;
615 *outptr++ = CAMEL_UUDECODE_CHAR (b0) << 2 | CAMEL_UUDECODE_CHAR (b1) >> 4;
616 *outptr++ = CAMEL_UUDECODE_CHAR (b1) << 4 | CAMEL_UUDECODE_CHAR (b2) >> 2;
617 *outptr++ = CAMEL_UUDECODE_CHAR (b2) << 6 | CAMEL_UUDECODE_CHAR (b3);
620 *outptr++ = CAMEL_UUDECODE_CHAR (b0) << 2 | CAMEL_UUDECODE_CHAR (b1) >> 4;
623 *outptr++ = CAMEL_UUDECODE_CHAR (b1) << 4 | CAMEL_UUDECODE_CHAR (b2) >> 2;
637 *state = (*state & CAMEL_UUDECODE_STATE_MASK) | ((uulen & 0xff) << 8) | (i & 0xff);
644 * camel_quoted_encode_close:
646 * @len: length of the input
647 * @out: output string
648 * @state: holds the number of bits that are stored in @save
649 * @save: leftover bits that have not yet been encoded
651 * Quoted-printable encodes a block of text. Call this when finished
652 * encoding data with #camel_quoted_encode_step to flush off
653 * the last little bit.
655 * Returns the number of bytes encoded
658 camel_quoted_encode_close(unsigned char *in, size_t len, unsigned char *out, int *state, int *save)
660 register unsigned char *outptr = out;
664 outptr += camel_quoted_encode_step(in, len, outptr, state, save);
668 /* space/tab must be encoded if it's the last character on
670 if (camel_mime_is_qpsafe(last) && last!=' ' && last!=9) {
674 *outptr++ = tohex[(last>>4) & 0xf];
675 *outptr++ = tohex[last & 0xf];
687 * camel_quoted_encode_step:
689 * @len: length of the input
690 * @out: output string
691 * @state: holds the number of bits that are stored in @save
692 * @save: leftover bits that have not yet been encoded
694 * Quoted-printable encodes a block of text. Performs an 'encode
695 * step', saves left-over state in state and save (initialise to -1 on
698 * Returns the number of bytes encoded
701 camel_quoted_encode_step (unsigned char *in, size_t len, unsigned char *out, int *statep, int *save)
703 register guchar *inptr, *outptr, *inend;
705 register int sofar = *save; /* keeps track of how many chars on a line */
706 register int last = *statep; /* keeps track if last char to end was a space cr etc */
711 while (inptr < inend) {
716 *outptr++ = tohex[(last >> 4) & 0xf];
717 *outptr++ = tohex[last & 0xf];
721 } else if (c == '\n') {
722 if (last != -1 && last != '\r') {
724 *outptr++ = tohex[(last >> 4) & 0xf];
725 *outptr++ = tohex[last & 0xf];
732 if (camel_mime_is_qpsafe(last)) {
737 *outptr++ = tohex[(last >> 4) & 0xf];
738 *outptr++ = tohex[last & 0xf];
743 if (camel_mime_is_qpsafe(c)) {
750 /* delay output of space char */
751 if (c==' ' || c=='\t') {
767 *outptr++ = tohex[(c >> 4) & 0xf];
768 *outptr++ = tohex[c & 0xf];
776 return (outptr - out);
780 FIXME: this does not strip trailing spaces from lines (as it should, rfc 2045, section 6.7)
781 Should it also canonicalise the end of line to CR LF??
783 Note: Trailing rubbish (at the end of input), like = or =x or =\r will be lost.
787 * camel_quoted_decode_step:
789 * @len: max length of data to decode
790 * @out: output stream
791 * @savestate: holds the number of bits that are stored in @save
792 * @saved: leftover bits that have not yet been decoded
794 * Decodes a block of quoted-printable encoded data. Performs a
795 * 'decode step' on a chunk of QP encoded data.
797 * Returns the number of bytes decoded
800 camel_quoted_decode_step(unsigned char *in, size_t len, unsigned char *out, int *savestate, int *saveme)
802 register unsigned char *inptr, *outptr;
803 unsigned char *inend, c;
809 d(printf("quoted-printable, decoding text '%.*s'\n", len, in));
814 while (inptr<inend) {
817 while (inptr<inend) {
823 #ifdef CANONICALISE_EOL
824 /*else if (c=='\r') {
826 } else if (c=='\n') {
839 /* soft break ... unix end of line */
848 if (isxdigit(c) && isxdigit(save)) {
850 save = toupper(save);
851 *outptr++ = (((save>='A'?save-'A'+10:save-'0')&0x0f) << 4)
852 | ((c>='A'?c-'A'+10:c-'0')&0x0f);
853 } else if (c=='\n' && save == '\r') {
854 /* soft break ... canonical end of line */
856 /* just output the data */
863 #ifdef CANONICALISE_EOL
865 /* convert \r -> to \r\n, leaves \r\n alone */
888 this is for the "Q" encoding of international words,
889 which is slightly different than plain quoted-printable (mainly by allowing 0x20 <> _)
892 quoted_decode(const unsigned char *in, size_t len, unsigned char *out)
894 register const unsigned char *inptr;
895 register unsigned char *outptr;
896 unsigned const char *inend;
903 d(printf("decoding text '%.*s'\n", len, in));
906 while (inptr<inend) {
909 /* silently ignore truncated data? */
911 c = toupper(*inptr++);
912 c1 = toupper(*inptr++);
913 *outptr++ = (((c>='A'?c-'A'+10:c-'0')&0x0f) << 4)
914 | ((c1>='A'?c1-'A'+10:c1-'0')&0x0f);
921 } else if (c==' ' || c==0x09) {
922 /* FIXME: this is an error! ignore for now ... */
935 /* rfc2047 version of quoted-printable */
936 /* safemask is the mask to apply to the camel_mime_special_table to determine what
937 characters can safely be included without encoding */
939 quoted_encode (const unsigned char *in, size_t len, unsigned char *out, unsigned short safemask)
941 register const unsigned char *inptr, *inend;
942 unsigned char *outptr;
948 while (inptr < inend) {
952 } else if (camel_mime_special_table[c] & safemask) {
956 *outptr++ = tohex[(c >> 4) & 0xf];
957 *outptr++ = tohex[c & 0xf];
961 d(printf("encoding '%.*s' = '%.*s'\n", len, in, outptr-out, out));
963 return (outptr - out);
968 header_decode_lwsp(const char **in)
970 const char *inptr = *in;
973 d2(printf("is ws: '%s'\n", *in));
975 while ((camel_mime_is_lwsp(*inptr) || *inptr =='(') && *inptr != '\0') {
976 while (camel_mime_is_lwsp(*inptr) && *inptr != '\0') {
977 d2(printf("(%c)", *inptr));
982 /* check for comments */
986 while (depth && (c=*inptr) && *inptr != '\0') {
987 if (c=='\\' && inptr[1]) {
1001 /* decode rfc 2047 encoded string segment */
1003 rfc2047_decode_word(const char *in, size_t len)
1005 const char *inptr = in+2;
1006 const char *inend = in+len-2;
1008 const char *charset;
1012 char *decword = NULL;
1013 char *decoded = NULL;
1014 char *outbase = NULL;
1016 size_t inlen, outlen;
1017 gboolean retried = FALSE;
1020 d(printf("rfc2047: decoding '%.*s'\n", len, in));
1022 /* quick check to see if this could possibly be a real encoded word */
1023 if (len < 8 || !(in[0] == '=' && in[1] == '?' && in[len-1] == '=' && in[len-2] == '?')) {
1024 d(printf("invalid\n"));
1028 /* skip past the charset to the encoding type */
1029 inptr = memchr (inptr, '?', inend-inptr);
1030 if (inptr != NULL && inptr < inend + 2 && inptr[2] == '?') {
1031 d(printf("found ?, encoding is '%c'\n", inptr[0]));
1033 tmplen = inend-inptr-2;
1034 decword = g_alloca (tmplen); /* this will always be more-than-enough room */
1035 switch(toupper(inptr[0])) {
1037 inlen = quoted_decode(inptr+2, tmplen, decword);
1041 unsigned int save = 0;
1043 inlen = camel_base64_decode_step((char *)inptr+2, tmplen, decword, &state, &save);
1044 /* if state != 0 then error? */
1048 /* uhhh, unknown encoding type - probably an invalid encoded word string */
1051 d(printf("The encoded length = %d\n", inlen));
1053 /* yuck, all this snot is to setup iconv! */
1054 tmplen = inptr - in - 3;
1055 encname = g_alloca (tmplen + 1);
1056 memcpy (encname, in + 2, tmplen);
1057 encname[tmplen] = '\0';
1059 /* rfc2231 updates rfc2047 encoded words...
1060 * The ABNF given in RFC 2047 for encoded-words is:
1061 * encoded-word := "=?" charset "?" encoding "?" encoded-text "?="
1062 * This specification changes this ABNF to:
1063 * encoded-word := "=?" charset ["*" language] "?" encoding "?" encoded-text "?="
1066 /* trim off the 'language' part if it's there... */
1067 p = strchr (encname, '*');
1071 charset = e_iconv_charset_name (encname);
1075 outlen = inlen * 6 + 16;
1076 outbase = g_alloca (outlen);
1080 ic = e_iconv_open ("UTF-8", charset);
1081 if (ic != (iconv_t) -1) {
1082 ret = e_iconv (ic, &inbuf, &inlen, &outbuf, &outlen);
1083 if (ret != (size_t) -1) {
1084 e_iconv (ic, NULL, 0, &outbuf, &outlen);
1086 decoded = g_strdup (outbase);
1090 w(g_warning ("Cannot decode charset, header display may be corrupt: %s: %s",
1091 charset, strerror (errno)));
1094 charset = e_iconv_locale_charset ();
1096 charset = "iso-8859-1";
1102 /* we return the encoded word here because we've got to return valid utf8 */
1103 decoded = g_strndup (in, inlen);
1108 d(printf("decoded '%s'\n", decoded));
1113 /* ok, a lot of mailers are BROKEN, and send iso-latin1 encoded
1114 headers, when they should just be sticking to US-ASCII
1115 according to the rfc's. Anyway, since the conversion to utf-8
1116 is trivial, just do it here without iconv */
1118 append_latin1 (GString *out, const char *in, size_t len)
1123 c = (unsigned int)*in++;
1126 out = g_string_append_c (out, 0xc0 | ((c >> 6) & 0x3)); /* 110000xx */
1127 out = g_string_append_c (out, 0x80 | (c & 0x3f)); /* 10xxxxxx */
1129 out = g_string_append_c (out, c);
1136 append_8bit (GString *out, const char *inbuf, size_t inlen, const char *charset)
1138 char *outbase, *outbuf;
1142 ic = e_iconv_open ("UTF-8", charset);
1143 if (ic == (iconv_t) -1)
1146 outlen = inlen * 6 + 16;
1147 outbuf = outbase = g_malloc(outlen);
1149 if (e_iconv (ic, &inbuf, &inlen, &outbuf, &outlen) == (size_t) -1) {
1150 w(g_warning("Conversion to '%s' failed: %s", charset, strerror (errno)));
1156 e_iconv (ic, NULL, NULL, &outbuf, &outlen);
1159 g_string_append(out, outbase);
1168 append_quoted_pair (GString *str, const char *in, gssize inlen)
1170 register const char *inptr = in;
1171 const char *inend = in + inlen;
1174 while (inptr < inend) {
1176 if (c == '\\' && inptr < inend)
1177 g_string_append_c (str, *inptr++);
1179 g_string_append_c (str, c);
1185 /* decodes a simple text, rfc822 + rfc2047 */
1187 header_decode_text (const char *in, size_t inlen, int ctext, const char *default_charset)
1190 const char *inptr, *inend, *start, *chunk, *locale_charset;
1191 GString *(* append) (GString *, const char *, gssize);
1195 locale_charset = e_iconv_locale_charset ();
1198 mask = (CAMEL_MIME_IS_SPECIAL | CAMEL_MIME_IS_SPACE | CAMEL_MIME_IS_CTRL);
1199 append = append_quoted_pair;
1201 mask = (CAMEL_MIME_IS_LWSP);
1202 append = g_string_append_len;
1205 out = g_string_new ("");
1207 inend = inptr + inlen;
1210 while (inptr < inend) {
1212 while (inptr < inend && camel_mime_is_type (*inptr, mask))
1215 if (inptr == inend) {
1216 append (out, start, inptr - start);
1218 } else if (dword == NULL) {
1219 append (out, start, inptr - start);
1225 while (inptr < inend && !camel_mime_is_type (*inptr, mask))
1228 dword = rfc2047_decode_word(start, inptr-start);
1230 g_string_append(out, dword);
1236 if ((default_charset == NULL || !append_8bit (out, chunk, inptr-chunk, default_charset))
1237 && (locale_charset == NULL || !append_8bit(out, chunk, inptr-chunk, locale_charset)))
1238 append_latin1(out, chunk, inptr-chunk);
1245 g_string_free (out, FALSE);
1252 * camel_header_decode_string:
1253 * @in: input header value string
1254 * @default_charset: default charset to use if improperly encoded
1256 * Decodes rfc2047 encoded-word tokens
1258 * Returns a string containing the UTF-8 version of the decoded header
1262 camel_header_decode_string (const char *in, const char *default_charset)
1266 return header_decode_text (in, strlen (in), FALSE, default_charset);
1271 * camel_header_format_ctext:
1272 * @in: input header value string
1273 * @default_charset: default charset to use if improperly encoded
1275 * Decodes a header which contains rfc2047 encoded-word tokens that
1276 * may or may not be within a comment.
1278 * Returns a string containing the UTF-8 version of the decoded header
1282 camel_header_format_ctext (const char *in, const char *default_charset)
1286 return header_decode_text (in, strlen (in), TRUE, default_charset);
1289 /* how long a sequence of pre-encoded words should be less than, to attempt to
1290 fit into a properly folded word. Only a guide. */
1291 #define CAMEL_FOLD_PREENCODED (24)
1293 /* FIXME: needs a way to cache iconv opens for different charsets? */
1295 rfc2047_encode_word(GString *outstring, const char *in, size_t len, const char *type, unsigned short safemask)
1297 iconv_t ic = (iconv_t) -1;
1298 char *buffer, *out, *ascii;
1299 size_t inlen, outlen, enclen, bufflen;
1300 const char *inptr, *p;
1303 d(printf("Converting [%d] '%.*s' to %s\n", len, len, in, type));
1305 /* convert utf8->encoding */
1306 bufflen = len * 6 + 16;
1307 buffer = g_alloca (bufflen);
1311 ascii = g_alloca (bufflen);
1313 if (g_ascii_strcasecmp (type, "UTF-8") != 0)
1314 ic = e_iconv_open (type, "UTF-8");
1317 size_t convlen, proclen;
1320 /* break up words into smaller bits, what we really want is encoded + overhead < 75,
1321 but we'll just guess what that means in terms of input chars, and assume its good enough */
1326 if (ic == (iconv_t) -1) {
1327 /* native encoding case, the easy one (?) */
1328 /* we work out how much we can convert, and still be in length */
1329 /* proclen will be the result of input characters that we can convert, to the nearest
1330 (approximated) valid utf8 char */
1335 while (p < (in+len) && convlen < (75 - strlen("=?utf-8?q?\?="))) {
1336 unsigned char c = *p++;
1343 if (camel_mime_special_table[c] & safemask)
1348 if (proclen >= 0 && proclen < i && convlen < (75 - strlen("=?utf-8?q?\?=")))
1350 /* well, we probably have broken utf8, just copy it anyway what the heck */
1351 if (proclen == -1) {
1352 w(g_warning("Appear to have truncated utf8 sequence"));
1355 memcpy(out, inptr, proclen);
1360 /* well we could do similar, but we can't (without undue effort), we'll just break it up into
1361 hopefully-small-enough chunks, and leave it at that */
1362 convlen = MIN(inlen, CAMEL_FOLD_PREENCODED);
1364 if (e_iconv (ic, &inptr, &convlen, &out, &outlen) == (size_t) -1 && errno != EINVAL) {
1365 w(g_warning("Conversion problem: conversion truncated: %s", strerror (errno)));
1366 /* blah, we include it anyway, better than infinite loop ... */
1369 /* make sure we flush out any shift state */
1370 e_iconv (ic, NULL, 0, &out, &outlen);
1372 inlen -= (inptr - p);
1375 enclen = out-buffer;
1384 out += sprintf (out, "=?%s?Q?", type);
1385 out += quoted_encode (buffer, enclen, out, safemask);
1386 sprintf (out, "?=");
1388 d(printf("converted part = %s\n", ascii));
1390 g_string_append (outstring, ascii);
1394 if (ic != (iconv_t) -1)
1399 /* TODO: Should this worry about quotes?? */
1401 * camel_header_encode_string:
1404 * Encodes a 'text' header according to the rules of rfc2047.
1406 * Returns the rfc2047 encoded header
1409 camel_header_encode_string (const unsigned char *in)
1411 const unsigned char *inptr = in, *start, *word;
1412 gboolean last_was_encoded = FALSE;
1413 gboolean last_was_space = FALSE;
1414 const char *charset;
1419 g_return_val_if_fail (g_utf8_validate (in, -1, NULL), NULL);
1424 /* do a quick us-ascii check (the common case?) */
1431 return g_strdup (in);
1433 /* This gets each word out of the input, and checks to see what charset
1434 can be used to encode it. */
1435 /* TODO: Work out when to merge subsequent words, or across word-parts */
1436 out = g_string_new ("");
1441 while (inptr && *inptr) {
1443 const char *newinptr;
1445 newinptr = g_utf8_next_char (inptr);
1446 c = g_utf8_get_char (inptr);
1447 if (newinptr == NULL || !g_unichar_validate (c)) {
1448 w(g_warning ("Invalid UTF-8 sequence encountered (pos %d, char '%c'): %s",
1449 (inptr-in), inptr[0], in));
1454 if (c < 256 && camel_mime_is_lwsp (c) && !last_was_space) {
1455 /* we've reached the end of a 'word' */
1456 if (word && !(last_was_encoded && encoding)) {
1457 /* output lwsp between non-encoded words */
1458 g_string_append_len (out, start, word - start);
1464 g_string_append_len (out, start, inptr - start);
1465 last_was_encoded = FALSE;
1468 if (last_was_encoded)
1469 g_string_append_c (out, ' ');
1471 rfc2047_encode_word (out, start, inptr - start, "ISO-8859-1", CAMEL_MIME_IS_ESAFE);
1472 last_was_encoded = TRUE;
1475 if (last_was_encoded)
1476 g_string_append_c (out, ' ');
1478 if (!(charset = camel_charset_best (start, inptr - start)))
1480 rfc2047_encode_word (out, start, inptr - start, charset, CAMEL_MIME_IS_ESAFE);
1481 last_was_encoded = TRUE;
1485 last_was_space = TRUE;
1489 } else if (c > 127 && c < 256) {
1490 encoding = MAX (encoding, 1);
1491 last_was_space = FALSE;
1492 } else if (c >= 256) {
1493 encoding = MAX (encoding, 2);
1494 last_was_space = FALSE;
1495 } else if (!camel_mime_is_lwsp (c)) {
1496 last_was_space = FALSE;
1499 if (!(c < 256 && camel_mime_is_lwsp (c)) && !word)
1505 if (inptr - start) {
1506 if (word && !(last_was_encoded && encoding)) {
1507 g_string_append_len (out, start, word - start);
1513 g_string_append_len (out, start, inptr - start);
1516 if (last_was_encoded)
1517 g_string_append_c (out, ' ');
1519 rfc2047_encode_word (out, start, inptr - start, "ISO-8859-1", CAMEL_MIME_IS_ESAFE);
1522 if (last_was_encoded)
1523 g_string_append_c (out, ' ');
1525 if (!(charset = camel_charset_best (start, inptr - start)))
1527 rfc2047_encode_word (out, start, inptr - start, charset, CAMEL_MIME_IS_ESAFE);
1533 g_string_free (out, FALSE);
1538 /* apply quoted-string rules to a string */
1540 quote_word(GString *out, gboolean do_quotes, const char *start, size_t len)
1544 /* TODO: What about folding on long lines? */
1546 g_string_append_c(out, '"');
1547 for (i=0;i<len;i++) {
1549 if (c == '\"' || c=='\\' || c=='\r')
1550 g_string_append_c(out, '\\');
1551 g_string_append_c(out, c);
1554 g_string_append_c(out, '"');
1557 /* incrementing possibility for the word type */
1558 enum _phrase_word_t {
1564 struct _phrase_word {
1565 const unsigned char *start, *end;
1566 enum _phrase_word_t type;
1571 word_types_compatable (enum _phrase_word_t type1, enum _phrase_word_t type2)
1575 return type2 == WORD_QSTRING;
1577 return type2 != WORD_2047;
1579 return type2 == WORD_2047;
1585 /* split the input into words with info about each word
1586 * merge common word types clean up */
1588 header_encode_phrase_get_words (const unsigned char *in)
1590 const unsigned char *inptr = in, *start, *last;
1591 struct _phrase_word *word;
1592 enum _phrase_word_t type;
1593 int encoding, count = 0;
1594 GList *words = NULL;
1596 /* break the input into words */
1601 while (inptr && *inptr) {
1603 const char *newinptr;
1605 newinptr = g_utf8_next_char (inptr);
1606 c = g_utf8_get_char (inptr);
1608 if (!g_unichar_validate (c)) {
1609 w(g_warning ("Invalid UTF-8 sequence encountered (pos %d, char '%c'): %s",
1610 (inptr - in), inptr[0], in));
1616 if (g_unichar_isspace (c)) {
1618 word = g_new0 (struct _phrase_word, 1);
1619 word->start = start;
1622 word->encoding = encoding;
1623 words = g_list_append (words, word);
1633 if (!camel_mime_is_atom (c))
1634 type = MAX (type, WORD_QSTRING);
1635 } else if (c > 127 && c < 256) {
1637 encoding = MAX (encoding, 1);
1638 } else if (c >= 256) {
1640 encoding = MAX (encoding, 2);
1648 word = g_new0 (struct _phrase_word, 1);
1649 word->start = start;
1652 word->encoding = encoding;
1653 words = g_list_append (words, word);
1659 #define MERGED_WORD_LT_FOLDLEN(wordlen, type) ((type) == WORD_2047 ? (wordlen) < CAMEL_FOLD_PREENCODED : (wordlen) < (CAMEL_FOLD_SIZE - 8))
1662 header_encode_phrase_merge_words (GList **wordsp)
1664 GList *wordl, *nextl, *words = *wordsp;
1665 struct _phrase_word *word, *next;
1666 gboolean merged = FALSE;
1668 /* scan the list, checking for words of similar types that can be merged */
1672 nextl = g_list_next (wordl);
1676 /* merge nodes of the same type AND we are not creating too long a string */
1677 if (word_types_compatable (word->type, next->type)) {
1678 if (MERGED_WORD_LT_FOLDLEN (next->end - word->start, MAX (word->type, next->type))) {
1679 /* the resulting word type is the MAX of the 2 types */
1680 word->type = MAX(word->type, next->type);
1681 word->encoding = MAX(word->encoding, next->encoding);
1682 word->end = next->end;
1683 words = g_list_remove_link (words, nextl);
1684 g_list_free_1 (nextl);
1687 nextl = g_list_next (wordl);
1691 /* if it is going to be too long, make sure we include the
1692 separating whitespace */
1693 word->end = next->start;
1701 wordl = g_list_next (wordl);
1709 /* encodes a phrase sequence (different quoting/encoding rules to strings) */
1711 * camel_header_encode_phrase:
1712 * @in: header to encode
1714 * Encodes a 'phrase' header according to the rules in rfc2047.
1716 * Returns the encoded 'phrase'
1719 camel_header_encode_phrase (const unsigned char *in)
1721 struct _phrase_word *word = NULL, *last_word = NULL;
1722 GList *words, *wordl;
1723 const char *charset;
1730 words = header_encode_phrase_get_words (in);
1734 while (header_encode_phrase_merge_words (&words))
1737 out = g_string_new ("");
1739 /* output words now with spaces between them */
1747 /* append correct number of spaces between words */
1748 if (last_word && !(last_word->type == WORD_2047 && word->type == WORD_2047)) {
1749 /* one or both of the words are not encoded so we write the spaces out untouched */
1750 len = word->start - last_word->end;
1751 out = g_string_append_len (out, last_word->end, len);
1754 switch (word->type) {
1756 out = g_string_append_len (out, word->start, word->end - word->start);
1759 quote_word (out, TRUE, word->start, word->end - word->start);
1762 if (last_word && last_word->type == WORD_2047) {
1763 /* include the whitespace chars between these 2 words in the
1764 resulting rfc2047 encoded word. */
1765 len = word->end - last_word->end;
1766 start = last_word->end;
1768 /* encoded words need to be separated by linear whitespace */
1769 g_string_append_c (out, ' ');
1771 len = word->end - word->start;
1772 start = word->start;
1775 if (word->encoding == 1) {
1776 rfc2047_encode_word (out, start, len, "ISO-8859-1", CAMEL_MIME_IS_PSAFE);
1778 if (!(charset = camel_charset_best (start, len)))
1780 rfc2047_encode_word (out, start, len, charset, CAMEL_MIME_IS_PSAFE);
1786 wordl = g_list_next (wordl);
1791 /* and we no longer need the list */
1793 g_list_free (words);
1796 g_string_free (out, FALSE);
1802 /* these are all internal parser functions */
1805 decode_token (const char **in)
1807 const char *inptr = *in;
1810 header_decode_lwsp (&inptr);
1812 while (camel_mime_is_ttoken (*inptr))
1814 if (inptr > start) {
1816 return g_strndup (start, inptr - start);
1824 * camel_header_token_decode:
1827 * Gets the first token in the string according to the rules of
1830 * Returns a new string containing the first token in @in
1833 camel_header_token_decode(const char *in)
1838 return decode_token(&in);
1842 <"> * ( <any char except <"> \, cr / \ <any char> ) <">
1845 header_decode_quoted_string(const char **in)
1847 const char *inptr = *in;
1848 char *out = NULL, *outptr;
1852 header_decode_lwsp(&inptr);
1853 if (*inptr == '"') {
1857 /* first, calc length */
1860 while ( (c = *intmp++) && c!= '"') {
1861 if (c=='\\' && *intmp) {
1866 outlen = intmp-inptr-skip;
1867 out = outptr = g_malloc(outlen+1);
1868 while ( (c = *inptr) && c!= '"') {
1870 if (c=='\\' && *inptr) {
1884 header_decode_atom(const char **in)
1886 const char *inptr = *in, *start;
1888 header_decode_lwsp(&inptr);
1890 while (camel_mime_is_atom(*inptr))
1894 return g_strndup(start, inptr-start);
1900 header_decode_word (const char **in)
1902 const char *inptr = *in;
1904 header_decode_lwsp (&inptr);
1905 if (*inptr == '"') {
1907 return header_decode_quoted_string (in);
1910 return header_decode_atom (in);
1915 header_decode_value(const char **in)
1917 const char *inptr = *in;
1919 header_decode_lwsp(&inptr);
1920 if (*inptr == '"') {
1921 d(printf("decoding quoted string\n"));
1922 return header_decode_quoted_string(in);
1923 } else if (camel_mime_is_ttoken(*inptr)) {
1924 d(printf("decoding token\n"));
1925 /* this may not have the right specials for all params? */
1926 return decode_token(in);
1931 /* should this return -1 for no int? */
1934 * camel_header_decode_int:
1935 * @in: pointer to input string
1937 * Extracts an integer token from @in and updates the pointer to point
1938 * to after the end of the integer token (sort of like strtol).
1940 * Returns the int value
1943 camel_header_decode_int(const char **in)
1945 const char *inptr = *in;
1948 header_decode_lwsp(&inptr);
1949 while ( (c=*inptr++ & 0xff)
1957 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : tolower (c) - 'a' + 10)
1960 hex_decode (const char *in, size_t len)
1962 const unsigned char *inend = in + len;
1963 unsigned char *inptr, *outptr;
1966 outptr = outbuf = g_malloc (len + 1);
1968 inptr = (unsigned char *) in;
1969 while (inptr < inend) {
1970 if (*inptr == '%') {
1971 if (isxdigit (inptr[1]) && isxdigit (inptr[2])) {
1972 *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
1975 *outptr++ = *inptr++;
1977 *outptr++ = *inptr++;
1985 /* Tries to convert @in @from charset @to charset. Any failure, we get no data out rather than partial conversion */
1987 header_convert(const char *to, const char *from, const char *in, size_t inlen)
1991 char *outbuf, *outbase, *result = NULL;
1993 ic = e_iconv_open(to, from);
1994 if (ic == (iconv_t) -1)
1997 outlen = inlen * 6 + 16;
1998 outbuf = outbase = g_malloc(outlen);
2000 ret = e_iconv(ic, &in, &inlen, &outbuf, &outlen);
2001 if (ret != (size_t) -1) {
2002 e_iconv(ic, NULL, 0, &outbuf, &outlen);
2004 result = g_strdup(outbase);
2012 /* an rfc2184 encoded string looks something like:
2013 * us-ascii'en'This%20is%20even%20more%20
2017 rfc2184_decode (const char *in, size_t len)
2019 const char *inptr = in;
2020 const char *inend = in + len;
2021 const char *charset;
2022 char *decoded, *decword, *encoding;
2024 inptr = memchr (inptr, '\'', len);
2028 encoding = g_alloca(inptr-in+1);
2029 memcpy(encoding, in, inptr-in);
2030 encoding[inptr-in] = 0;
2031 charset = e_iconv_charset_name (encoding);
2033 inptr = memchr (inptr + 1, '\'', inend - inptr - 1);
2040 decword = hex_decode (inptr, inend - inptr);
2041 decoded = header_convert("UTF-8", charset, decword, strlen(decword));
2049 * camel_header_param:
2050 * @params: parameters
2051 * @name: name of param to find
2053 * Searches @params for a param named @name and gets the value.
2055 * Returns the value of the @name param
2058 camel_header_param (struct _camel_header_param *p, const char *name)
2060 while (p && g_ascii_strcasecmp (p->name, name) != 0)
2069 * camel_header_set_param:
2070 * @paramsp: poinetr to a list of params
2071 * @name: name of param to set
2072 * @value: value to set
2074 * Set a parameter in the list.
2076 * Returns the set param
2078 struct _camel_header_param *
2079 camel_header_set_param (struct _camel_header_param **l, const char *name, const char *value)
2081 struct _camel_header_param *p = (struct _camel_header_param *)l, *pn;
2088 if (!g_ascii_strcasecmp (pn->name, name)) {
2091 pn->value = g_strdup (value);
2106 pn = g_malloc (sizeof (*pn));
2108 pn->name = g_strdup (name);
2109 pn->value = g_strdup (value);
2117 * camel_content_type_param:
2118 * @content_type: a #CamelContentType
2119 * @name: name of param to find
2121 * Searches the params on s #CamelContentType for a param named @name
2122 * and gets the value.
2124 * Returns the value of the @name param
2127 camel_content_type_param (CamelContentType *t, const char *name)
2131 return camel_header_param (t->params, name);
2136 * camel_content_type_set_param:
2137 * @content_type: a #CamelContentType
2138 * @name: name of param to set
2139 * @value: value of param to set
2141 * Set a parameter on @content_type.
2144 camel_content_type_set_param (CamelContentType *t, const char *name, const char *value)
2146 camel_header_set_param (&t->params, name, value);
2151 * camel_content_type_is:
2152 * @content_type: A content type specifier, or %NULL.
2153 * @type: A type to check against.
2154 * @subtype: A subtype to check against, or "*" to match any subtype.
2156 * The subtype of "*" will match any subtype. If @ct is %NULL, then
2157 * it will match the type "text/plain".
2159 * Returns %TRUE if the content type @ct is of type @type/@subtype or
2163 camel_content_type_is(CamelContentType *ct, const char *type, const char *subtype)
2165 /* no type == text/plain or text/"*" */
2166 if (ct==NULL || (ct->type == NULL && ct->subtype == NULL)) {
2167 return (!g_ascii_strcasecmp(type, "text")
2168 && (!g_ascii_strcasecmp(subtype, "plain")
2169 || !strcmp(subtype, "*")));
2172 return (ct->type != NULL
2173 && (!g_ascii_strcasecmp(ct->type, type)
2174 && ((ct->subtype != NULL
2175 && !g_ascii_strcasecmp(ct->subtype, subtype))
2176 || !strcmp("*", subtype))));
2181 * camel_header_param_list_free:
2182 * @params: a list of params
2184 * Free the list of params.
2187 camel_header_param_list_free(struct _camel_header_param *p)
2189 struct _camel_header_param *n;
2202 * camel_content_type_new:
2203 * @type: the major type of the new content-type
2204 * @subtype: the subtype
2206 * Create a new #CamelContentType.
2208 * Returns the new #CamelContentType
2211 camel_content_type_new(const char *type, const char *subtype)
2213 CamelContentType *t = g_malloc(sizeof(*t));
2215 t->type = g_strdup(type);
2216 t->subtype = g_strdup(subtype);
2224 * camel_content_type_ref:
2225 * @content_type: a #CamelContentType
2227 * Refs the content type.
2230 camel_content_type_ref(CamelContentType *ct)
2238 * camel_content_type_unref:
2239 * @content_type: a #CamelContentType
2241 * Unrefs, and potentially frees, the content type.
2244 camel_content_type_unref(CamelContentType *ct)
2247 if (ct->refcount <= 1) {
2248 camel_header_param_list_free(ct->params);
2250 g_free(ct->subtype);
2259 /* for decoding email addresses, canonically */
2261 header_decode_domain(const char **in)
2263 const char *inptr = *in;
2266 GString *domain = g_string_new("");
2268 /* domain ref | domain literal */
2269 header_decode_lwsp(&inptr);
2271 if (*inptr == '[') { /* domain literal */
2272 domain = g_string_append_c(domain, '[');
2274 header_decode_lwsp(&inptr);
2275 while (camel_mime_is_dtext(*inptr) && *inptr) {
2276 domain = g_string_append_c(domain, *inptr);
2279 if (*inptr == ']') {
2280 domain = g_string_append_c(domain, ']');
2283 w(g_warning("closing ']' not found in domain: %s", *in));
2286 char *a = header_decode_atom(&inptr);
2288 domain = g_string_append(domain, a);
2291 w(g_warning("missing atom from domain-ref"));
2295 header_decode_lwsp(&inptr);
2296 if (*inptr == '.') { /* next sub-domain? */
2297 domain = g_string_append_c(domain, '.');
2299 header_decode_lwsp(&inptr);
2307 g_string_free(domain, FALSE);
2312 header_decode_addrspec(const char **in)
2314 const char *inptr = *in;
2316 GString *addr = g_string_new("");
2318 header_decode_lwsp(&inptr);
2321 word = header_decode_word (&inptr);
2323 addr = g_string_append(addr, word);
2324 header_decode_lwsp(&inptr);
2326 while (*inptr == '.' && word) {
2328 addr = g_string_append_c(addr, '.');
2329 word = header_decode_word (&inptr);
2331 addr = g_string_append(addr, word);
2332 header_decode_lwsp(&inptr);
2335 w(g_warning("Invalid address spec: %s", *in));
2338 if (*inptr == '@') {
2340 addr = g_string_append_c(addr, '@');
2341 word = header_decode_domain(&inptr);
2343 addr = g_string_append(addr, word);
2346 w(g_warning("Invalid address, missing domain: %s", *in));
2349 w(g_warning("Invalid addr-spec, missing @: %s", *in));
2352 w(g_warning("invalid addr-spec, no local part"));
2353 g_string_free(addr, TRUE);
2358 /* FIXME: return null on error? */
2362 g_string_free(addr, FALSE);
2368 word *('.' word) @ domain |
2369 *(word) '<' [ *('@' domain ) ':' ] word *( '.' word) @ domain |
2371 1*word ':' [ word ... etc (mailbox, as above) ] ';'
2375 word *( '.' word ) '@' domain
2376 *(word) '<' [ *('@' domain ) ':' ] word *( '.' word) @ domain
2379 static struct _camel_header_address *
2380 header_decode_mailbox(const char **in, const char *charset)
2382 const char *inptr = *in;
2384 int closeme = FALSE;
2386 GString *name = NULL;
2387 struct _camel_header_address *address = NULL;
2388 const char *comment = NULL;
2390 addr = g_string_new("");
2392 /* for each address */
2393 pre = header_decode_word (&inptr);
2394 header_decode_lwsp(&inptr);
2395 if (!(*inptr == '.' || *inptr == '@' || *inptr==',' || *inptr=='\0')) {
2396 /* ',' and '\0' required incase it is a simple address, no @ domain part (buggy writer) */
2397 name = g_string_new ("");
2401 /* perform internationalised decoding, and append */
2402 text = camel_header_decode_string (pre, charset);
2403 g_string_append (name, text);
2407 pre = header_decode_word (&inptr);
2409 size_t l = strlen (last);
2410 size_t p = strlen (pre);
2412 /* dont append ' ' between sucsessive encoded words */
2413 if ((l>6 && last[l-2] == '?' && last[l-1] == '=')
2414 && (p>6 && pre[0] == '=' && pre[1] == '?')) {
2415 /* dont append ' ' */
2417 name = g_string_append_c(name, ' ');
2420 /* Fix for stupidly-broken-mailers that like to put '.''s in names unquoted */
2422 while (!pre && *inptr && *inptr != '<') {
2423 w(g_warning("Working around stupid mailer bug #5: unescaped characters in names"));
2424 name = g_string_append_c(name, *inptr++);
2425 pre = header_decode_word (&inptr);
2430 header_decode_lwsp(&inptr);
2431 if (*inptr == '<') {
2435 header_decode_lwsp(&inptr);
2436 if (*inptr == '@') {
2437 while (*inptr == '@') {
2439 header_decode_domain(&inptr);
2440 header_decode_lwsp(&inptr);
2441 if (*inptr == ',') {
2443 header_decode_lwsp(&inptr);
2446 if (*inptr == ':') {
2449 w(g_warning("broken route-address, missing ':': %s", *in));
2452 pre = header_decode_word (&inptr);
2453 /*header_decode_lwsp(&inptr);*/
2455 w(g_warning("broken address? %s", *in));
2460 addr = g_string_append(addr, pre);
2462 w(g_warning("No local-part for email address: %s", *in));
2465 /* should be at word '.' localpart */
2466 while (*inptr == '.' && pre) {
2469 pre = header_decode_word (&inptr);
2470 addr = g_string_append_c(addr, '.');
2472 addr = g_string_append(addr, pre);
2474 header_decode_lwsp(&inptr);
2478 /* now at '@' domain part */
2479 if (*inptr == '@') {
2483 addr = g_string_append_c(addr, '@');
2485 dom = header_decode_domain(&inptr);
2486 addr = g_string_append(addr, dom);
2488 } else if (*inptr != '>' || !closeme) {
2489 /* If we get a <, the address was probably a name part, lets try again shall we? */
2490 /* Another fix for seriously-broken-mailers */
2491 if (*inptr && *inptr != ',') {
2494 w(g_warning("We didn't get an '@' where we expected in '%s', trying again", *in));
2495 w(g_warning("Name is '%s', Addr is '%s' we're at '%s'\n", name?name->str:"<UNSET>", addr->str, inptr));
2497 /* need to keep *inptr, as try_address_again will drop the current character */
2501 g_string_append_c(addr, *inptr);
2503 /* check for address is encoded word ... */
2504 text = camel_header_decode_string(addr->str, charset);
2507 addr = g_string_new("");
2509 g_string_truncate(name, 0);
2510 g_string_append(name, text);
2513 g_string_append(name, text?text:addr->str);
2514 g_string_truncate(addr, 0);
2518 /* or maybe that we've added up a bunch of broken bits to make an encoded word */
2519 text = rfc2047_decode_word(name->str, name->len);
2521 g_string_truncate(name, 0);
2522 g_string_append(name, text);
2526 goto try_address_again;
2528 w(g_warning("invalid address, no '@' domain part at %c: %s", *inptr, *in));
2532 header_decode_lwsp(&inptr);
2533 if (*inptr == '>') {
2536 w(g_warning("invalid route address, no closing '>': %s", *in));
2538 } else if (name == NULL && comment != NULL && inptr>comment) { /* check for comment after address */
2540 const char *comstart, *comend;
2542 /* this is a bit messy, we go from the last known position, because
2543 decode_domain/etc skip over any comments on the way */
2544 /* FIXME: This wont detect comments inside the domain itself,
2545 but nobody seems to use that feature anyway ... */
2547 d(printf("checking for comment from '%s'\n", comment));
2549 comstart = strchr(comment, '(');
2552 header_decode_lwsp(&inptr);
2554 while (comend > comstart && comend[0] != ')')
2557 if (comend > comstart) {
2558 d(printf(" looking at subset '%.*s'\n", comend-comstart, comstart));
2559 tmp = g_strndup (comstart, comend-comstart);
2560 text = camel_header_decode_string (tmp, charset);
2561 name = g_string_new (text);
2570 if (addr->len > 0) {
2571 if (!g_utf8_validate (addr->str, addr->len, NULL)) {
2572 /* workaround for invalid addr-specs containing 8bit chars (see bug #42170 for details) */
2573 const char *locale_charset;
2576 locale_charset = e_iconv_locale_charset ();
2578 out = g_string_new ("");
2580 if ((charset == NULL || !append_8bit (out, addr->str, addr->len, charset))
2581 && (locale_charset == NULL || !append_8bit (out, addr->str, addr->len, locale_charset)))
2582 append_latin1 (out, addr->str, addr->len);
2584 g_string_free (addr, TRUE);
2588 address = camel_header_address_new_name(name ? name->str : "", addr->str);
2591 d(printf("got mailbox: %s\n", addr->str));
2593 g_string_free(addr, TRUE);
2595 g_string_free(name, TRUE);
2600 static struct _camel_header_address *
2601 header_decode_address(const char **in, const char *charset)
2603 const char *inptr = *in;
2605 GString *group = g_string_new("");
2606 struct _camel_header_address *addr = NULL, *member;
2608 /* pre-scan, trying to work out format, discard results */
2609 header_decode_lwsp(&inptr);
2610 while ((pre = header_decode_word (&inptr))) {
2611 group = g_string_append(group, pre);
2612 group = g_string_append(group, " ");
2615 header_decode_lwsp(&inptr);
2616 if (*inptr == ':') {
2617 d(printf("group detected: %s\n", group->str));
2618 addr = camel_header_address_new_group(group->str);
2619 /* that was a group spec, scan mailbox's */
2621 /* FIXME: check rfc 2047 encodings of words, here or above in the loop */
2622 header_decode_lwsp(&inptr);
2623 if (*inptr != ';') {
2626 member = header_decode_mailbox(&inptr, charset);
2628 camel_header_address_add_member(addr, member);
2629 header_decode_lwsp(&inptr);
2635 if (*inptr == ';') {
2638 w(g_warning("Invalid group spec, missing closing ';': %s", *in));
2645 addr = header_decode_mailbox(in, charset);
2648 g_string_free(group, TRUE);
2654 header_msgid_decode_internal(const char **in)
2656 const char *inptr = *in;
2659 d(printf("decoding Message-ID: '%s'\n", *in));
2661 header_decode_lwsp(&inptr);
2662 if (*inptr == '<') {
2664 header_decode_lwsp(&inptr);
2665 msgid = header_decode_addrspec(&inptr);
2667 header_decode_lwsp(&inptr);
2668 if (*inptr == '>') {
2671 w(g_warning("Missing closing '>' on message id: %s", *in));
2674 w(g_warning("Cannot find message id in: %s", *in));
2677 w(g_warning("missing opening '<' on message id: %s", *in));
2686 * camel_header_msgid_decode:
2689 * Extract a message-id token from @in.
2691 * Returns the msg-id
2694 camel_header_msgid_decode(const char *in)
2699 return header_msgid_decode_internal(&in);
2704 * camel_header_contentid_decode:
2707 * Extract a content-id from @in.
2709 * Returns the extracted content-id
2712 camel_header_contentid_decode (const char *in)
2714 const char *inptr = in;
2715 gboolean at = FALSE;
2719 d(printf("decoding Content-ID: '%s'\n", in));
2721 header_decode_lwsp (&inptr);
2723 /* some lame mailers quote the Content-Id */
2727 /* make sure the content-id is not "" which can happen if we get a
2728 * content-id such as <.@> (which Eudora likes to use...) */
2729 if ((buf = camel_header_msgid_decode (inptr)) != NULL && *buf)
2734 /* ugh, not a valid msg-id - try to get something useful out of it then? */
2736 header_decode_lwsp (&inptr);
2737 if (*inptr == '<') {
2739 header_decode_lwsp (&inptr);
2742 /* Eudora has been known to use <.@> as a content-id */
2743 if (!(buf = header_decode_word (&inptr)) && !strchr (".@", *inptr))
2746 addr = g_string_new ("");
2747 header_decode_lwsp (&inptr);
2748 while (buf != NULL || *inptr == '.' || (*inptr == '@' && !at)) {
2750 g_string_append (addr, buf);
2756 if (*inptr == '.') {
2757 g_string_append_c (addr, *inptr++);
2758 buf = header_decode_word (&inptr);
2759 } else if (*inptr == '@') {
2760 g_string_append_c (addr, *inptr++);
2761 buf = header_decode_word (&inptr);
2764 } else if (strchr (".[]", *inptr)) {
2765 g_string_append_c (addr, *inptr++);
2766 buf = header_decode_atom (&inptr);
2769 header_decode_lwsp (&inptr);
2773 g_string_free (addr, FALSE);
2779 camel_header_references_list_append_asis(struct _camel_header_references **list, char *ref)
2781 struct _camel_header_references *w = (struct _camel_header_references *)list, *n;
2784 n = g_malloc(sizeof(*n));
2791 camel_header_references_list_size(struct _camel_header_references **list)
2794 struct _camel_header_references *w = *list;
2803 camel_header_references_list_clear(struct _camel_header_references **list)
2805 struct _camel_header_references *w = *list, *n;
2816 header_references_decode_single (const char **in, struct _camel_header_references **head)
2818 struct _camel_header_references *ref;
2819 const char *inptr = *in;
2823 header_decode_lwsp (&inptr);
2824 if (*inptr == '<') {
2825 id = header_msgid_decode_internal (&inptr);
2827 ref = g_malloc (sizeof (struct _camel_header_references));
2834 word = header_decode_word (&inptr);
2837 else if (*inptr != '\0')
2838 inptr++; /* Stupid mailer tricks */
2845 /* TODO: why is this needed? Can't the other interface also work? */
2846 struct _camel_header_references *
2847 camel_header_references_inreplyto_decode (const char *in)
2849 struct _camel_header_references *ref = NULL;
2851 if (in == NULL || in[0] == '\0')
2854 header_references_decode_single (&in, &ref);
2859 /* generate a list of references, from most recent up */
2860 struct _camel_header_references *
2861 camel_header_references_decode (const char *in)
2863 struct _camel_header_references *refs = NULL;
2865 if (in == NULL || in[0] == '\0')
2869 header_references_decode_single (&in, &refs);
2874 struct _camel_header_references *
2875 camel_header_references_dup(const struct _camel_header_references *list)
2877 struct _camel_header_references *new = NULL, *tmp;
2880 tmp = g_new(struct _camel_header_references, 1);
2882 tmp->id = g_strdup(list->id);
2889 struct _camel_header_address *
2890 camel_header_mailbox_decode(const char *in, const char *charset)
2895 return header_decode_mailbox(&in, charset);
2898 struct _camel_header_address *
2899 camel_header_address_decode(const char *in, const char *charset)
2901 const char *inptr = in, *last;
2902 struct _camel_header_address *list = NULL, *addr;
2904 d(printf("decoding To: '%s'\n", in));
2909 header_decode_lwsp(&inptr);
2915 addr = header_decode_address(&inptr, charset);
2917 camel_header_address_list_append(&list, addr);
2918 header_decode_lwsp(&inptr);
2923 } while (inptr != last);
2926 w(g_warning("Invalid input detected at %c (%d): %s\n or at: %s", *inptr, inptr-in, in, inptr));
2929 if (inptr == last) {
2930 w(g_warning("detected invalid input loop at : %s", last));
2936 struct _camel_header_newsgroup *
2937 camel_header_newsgroups_decode(const char *in)
2939 const char *inptr = in;
2941 struct _camel_header_newsgroup *head, *last, *ng;
2945 last = (struct _camel_header_newsgroup *)&head;
2948 header_decode_lwsp(&inptr);
2950 while ((c = *inptr++) && !camel_mime_is_lwsp(c) && c != ',')
2952 if (start != inptr-1) {
2953 ng = g_malloc(sizeof(*ng));
2954 ng->newsgroup = g_strndup(start, inptr-start-1);
2965 camel_header_newsgroups_free(struct _camel_header_newsgroup *ng)
2968 struct _camel_header_newsgroup *nng = ng->next;
2970 g_free(ng->newsgroup);
2976 /* this must be kept in sync with the header */
2977 static const char *encodings[] = {
2988 camel_transfer_encoding_to_string (CamelTransferEncoding encoding)
2990 if (encoding >= sizeof (encodings) / sizeof (encodings[0]))
2993 return encodings[encoding];
2996 CamelTransferEncoding
2997 camel_transfer_encoding_from_string (const char *string)
3001 if (string != NULL) {
3002 for (i = 0; i < sizeof (encodings) / sizeof (encodings[0]); i++)
3003 if (!g_ascii_strcasecmp (string, encodings[i]))
3007 return CAMEL_TRANSFER_ENCODING_DEFAULT;
3011 camel_header_mime_decode(const char *in, int *maj, int *min)
3013 const char *inptr = in;
3014 int major=-1, minor=-1;
3016 d(printf("decoding MIME-Version: '%s'\n", in));
3019 header_decode_lwsp(&inptr);
3020 if (isdigit(*inptr)) {
3021 major = camel_header_decode_int(&inptr);
3022 header_decode_lwsp(&inptr);
3023 if (*inptr == '.') {
3025 header_decode_lwsp(&inptr);
3026 if (isdigit(*inptr))
3027 minor = camel_header_decode_int(&inptr);
3037 d(printf("major = %d, minor = %d\n", major, minor));
3040 struct _rfc2184_param {
3041 struct _camel_header_param param;
3046 rfc2184_param_cmp(const void *ap, const void *bp)
3048 const struct _rfc2184_param *a = *(void **)ap;
3049 const struct _rfc2184_param *b = *(void **)bp;
3052 res = strcmp(a->param.name, b->param.name);
3054 if (a->index > b->index)
3056 else if (a->index < b->index)
3063 /* NB: Steals name and value */
3064 static struct _camel_header_param *
3065 header_append_param(struct _camel_header_param *last, char *name, char *value)
3067 struct _camel_header_param *node;
3070 8 bit data in parameters, illegal, tries to convert using locale, or just safens it up.
3071 rfc2047 ecoded parameters, illegal, decodes them anyway. Some Outlook & Mozilla do this?
3073 node = g_malloc(sizeof(*node));
3077 if (strncmp(value, "=?", 2) == 0
3078 && (node->value = header_decode_text(value, strlen(value), FALSE, NULL))) {
3080 } else if (g_ascii_strcasecmp (name, "boundary") != 0 && !g_utf8_validate(value, -1, NULL)) {
3081 const char *charset = e_iconv_locale_charset();
3083 if ((node->value = header_convert("UTF-8", charset?charset:"ISO-8859-1", value, strlen(value)))) {
3086 node->value = value;
3087 for (;*value;value++)
3088 if (!isascii((unsigned char)*value))
3092 node->value = value;
3097 static struct _camel_header_param *
3098 header_decode_param_list (const char **in)
3100 struct _camel_header_param *head = NULL, *last = (struct _camel_header_param *)&head;
3101 GPtrArray *split = NULL;
3102 const char *inptr = *in;
3103 struct _rfc2184_param *work;
3106 /* Dump parameters into the output list, in the order found. RFC 2184 split parameters are kept in an array */
3107 header_decode_lwsp(&inptr);
3108 while (*inptr == ';') {
3113 name = decode_token(&inptr);
3114 header_decode_lwsp(&inptr);
3115 if (*inptr == '=') {
3117 value = header_decode_value(&inptr);
3120 if (name && value) {
3121 char *index = strchr(name, '*');
3124 if (index[1] == 0) {
3125 /* VAL*="foo", decode immediately and append */
3127 tmp = rfc2184_decode(value, strlen(value));
3132 last = header_append_param(last, name, value);
3134 /* VAL*1="foo", save for later */
3136 work = g_malloc(sizeof(*work));
3137 work->param.name = name;
3138 work->param.value = value;
3139 work->index = atoi(index);
3141 split = g_ptr_array_new();
3142 g_ptr_array_add(split, work);
3145 last = header_append_param(last, name, value);
3152 header_decode_lwsp(&inptr);
3155 /* Rejoin any RFC 2184 split parameters in the proper order */
3156 /* Parameters with the same index will be concatenated in undefined order */
3158 GString *value = g_string_new("");
3159 struct _rfc2184_param *first;
3162 qsort(split->pdata, split->len, sizeof(split->pdata[0]), rfc2184_param_cmp);
3163 first = split->pdata[0];
3164 for (i=0;i<split->len;i++) {
3165 work = split->pdata[i];
3166 if (split->len-1 == i)
3167 g_string_append(value, work->param.value);
3168 if (split->len-1 == i || strcmp(work->param.name, first->param.name) != 0) {
3169 tmp = rfc2184_decode(value->str, value->len);
3171 tmp = g_strdup(value->str);
3173 last = header_append_param(last, g_strdup(first->param.name), tmp);
3174 g_string_truncate(value, 0);
3177 if (split->len-1 != i)
3178 g_string_append(value, work->param.value);
3180 g_string_free(value, TRUE);
3181 for (i=0;i<split->len;i++) {
3182 work = split->pdata[i];
3183 g_free(work->param.name);
3184 g_free(work->param.value);
3187 g_ptr_array_free(split, TRUE);
3195 struct _camel_header_param *
3196 camel_header_param_list_decode(const char *in)
3201 return header_decode_param_list(&in);
3205 header_encode_param (const unsigned char *in, gboolean *encoded)
3207 const unsigned char *inptr = in;
3208 unsigned char *outbuf = NULL;
3209 const char *charset;
3216 g_return_val_if_fail (in != NULL, NULL);
3218 /* if we have really broken utf8 passed in, we just treat it as binary data */
3220 charset = camel_charset_best(in, strlen(in));
3221 if (charset == NULL)
3222 return g_strdup(in);
3224 if (g_ascii_strcasecmp(charset, "UTF-8") != 0) {
3225 if ((outbuf = header_convert(charset, "UTF-8", in, strlen(in))))
3228 return g_strdup(in);
3231 /* FIXME: set the 'language' as well, assuming we can get that info...? */
3232 out = g_string_new (charset);
3233 g_string_append(out, "''");
3235 while ( (c = *inptr++) ) {
3236 if (camel_mime_is_attrchar(c))
3237 g_string_append_c (out, c);
3239 g_string_append_printf (out, "%%%c%c", tohex[(c >> 4) & 0xf], tohex[c & 0xf]);
3244 g_string_free (out, FALSE);
3251 camel_header_param_list_format_append (GString *out, struct _camel_header_param *p)
3253 int used = out->len;
3256 gboolean encoded = FALSE;
3257 gboolean quote = FALSE;
3258 int here = out->len;
3267 value = header_encode_param (p->value, &encoded);
3269 w(g_warning ("appending parameter %s=%s violates rfc2184", p->name, p->value));
3270 value = g_strdup (p->value);
3276 for (ch = value; *ch; ch++) {
3277 if (camel_mime_is_tspecial (*ch) || camel_mime_is_lwsp (*ch))
3284 nlen = strlen (p->name);
3285 vlen = strlen (value);
3287 if (used + nlen + vlen > CAMEL_FOLD_SIZE - 8) {
3288 out = g_string_append (out, ";\n\t");
3292 out = g_string_append (out, "; ");
3294 if (nlen + vlen > CAMEL_FOLD_SIZE - 8) {
3295 /* we need to do special rfc2184 parameter wrapping */
3296 int maxlen = CAMEL_FOLD_SIZE - (nlen + 8);
3297 char *inptr, *inend;
3301 inend = value + vlen;
3303 while (inptr < inend) {
3304 char *ptr = inptr + MIN (inend - inptr, maxlen);
3306 if (encoded && ptr < inend) {
3307 /* be careful not to break an encoded char (ie %20) */
3311 for ( ; j > 0 && q > inptr && *q != '%'; j--, q--);
3317 g_string_append (out, ";\n\t");
3322 g_string_append_printf (out, "%s*%d%s=", p->name, i++, encoded ? "*" : "");
3323 if (encoded || !quote)
3324 g_string_append_len (out, inptr, ptr - inptr);
3326 quote_word (out, TRUE, inptr, ptr - inptr);
3328 d(printf ("wrote: %s\n", out->str + here));
3330 used += (out->len - here);
3335 g_string_append_printf (out, "%s%s=", p->name, encoded ? "*" : "");
3337 if (encoded || !quote)
3338 g_string_append (out, value);
3340 quote_word (out, TRUE, value, vlen);
3342 used += (out->len - here);
3352 camel_header_param_list_format(struct _camel_header_param *p)
3354 GString *out = g_string_new("");
3357 camel_header_param_list_format_append(out, p);
3359 g_string_free(out, FALSE);
3364 camel_content_type_decode(const char *in)
3366 const char *inptr = in;
3367 char *type, *subtype = NULL;
3368 CamelContentType *t = NULL;
3373 type = decode_token(&inptr);
3374 header_decode_lwsp(&inptr);
3376 if (*inptr == '/') {
3378 subtype = decode_token(&inptr);
3380 if (subtype == NULL && (!g_ascii_strcasecmp(type, "text"))) {
3381 w(g_warning("text type with no subtype, resorting to text/plain: %s", in));
3382 subtype = g_strdup("plain");
3384 if (subtype == NULL) {
3385 w(g_warning("MIME type with no subtype: %s", in));
3388 t = camel_content_type_new(type, subtype);
3389 t->params = header_decode_param_list(&inptr);
3394 d(printf("cannot find MIME type in header (2) '%s'", in));
3400 camel_content_type_dump(CamelContentType *ct)
3402 struct _camel_header_param *p;
3404 printf("Content-Type: ");
3409 printf("%s / %s", ct->type, ct->subtype);
3413 printf(";\n\t%s=\"%s\"", p->name, p->value);
3421 camel_content_type_format (CamelContentType *ct)
3429 out = g_string_new ("");
3430 if (ct->type == NULL) {
3431 g_string_append_printf (out, "text/plain");
3432 w(g_warning ("Content-Type with no main type"));
3433 } else if (ct->subtype == NULL) {
3434 w(g_warning ("Content-Type with no sub type: %s", ct->type));
3435 if (!g_ascii_strcasecmp (ct->type, "multipart"))
3436 g_string_append_printf (out, "%s/mixed", ct->type);
3438 g_string_append_printf (out, "%s", ct->type);
3440 g_string_append_printf (out, "%s/%s", ct->type, ct->subtype);
3442 camel_header_param_list_format_append (out, ct->params);
3445 g_string_free (out, FALSE);
3451 camel_content_type_simple (CamelContentType *ct)
3453 if (ct->type == NULL) {
3454 w(g_warning ("Content-Type with no main type"));
3455 return g_strdup ("text/plain");
3456 } else if (ct->subtype == NULL) {
3457 w(g_warning ("Content-Type with no sub type: %s", ct->type));
3458 if (!g_ascii_strcasecmp (ct->type, "multipart"))
3459 return g_strdup_printf ("%s/mixed", ct->type);
3461 return g_strdup (ct->type);
3463 return g_strdup_printf ("%s/%s", ct->type, ct->subtype);
3467 camel_content_transfer_encoding_decode (const char *in)
3470 return decode_token (&in);
3475 CamelContentDisposition *
3476 camel_content_disposition_decode(const char *in)
3478 CamelContentDisposition *d = NULL;
3479 const char *inptr = in;
3484 d = g_malloc(sizeof(*d));
3486 d->disposition = decode_token(&inptr);
3487 if (d->disposition == NULL)
3488 w(g_warning("Empty disposition type"));
3489 d->params = header_decode_param_list(&inptr);
3494 camel_content_disposition_ref(CamelContentDisposition *d)
3501 camel_content_disposition_unref(CamelContentDisposition *d)
3504 if (d->refcount<=1) {
3505 camel_header_param_list_free(d->params);
3506 g_free(d->disposition);
3515 camel_content_disposition_format(CamelContentDisposition *d)
3523 out = g_string_new("");
3525 out = g_string_append(out, d->disposition);
3527 out = g_string_append(out, "attachment");
3528 camel_header_param_list_format_append(out, d->params);
3531 g_string_free(out, FALSE);
3535 /* hrm, is there a library for this shit? */
3542 { "EST", -500 }, /* these are all US timezones. bloody yanks */
3557 static const char tz_months [][4] = {
3558 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
3559 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
3562 static const char tz_days [][4] = {
3563 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
3567 camel_header_format_date(time_t time, int offset)
3571 d(printf("offset = %d\n", offset));
3573 d(printf("converting date %s", ctime(&time)));
3575 time += ((offset / 100) * (60*60)) + (offset % 100)*60;
3577 d(printf("converting date %s", ctime(&time)));
3579 gmtime_r (&time, &tm);
3581 return g_strdup_printf("%s, %02d %s %04d %02d:%02d:%02d %+05d",
3582 tz_days[tm.tm_wday],
3583 tm.tm_mday, tz_months[tm.tm_mon],
3585 tm.tm_hour, tm.tm_min, tm.tm_sec,
3589 /* convert a date to time_t representation */
3590 /* this is an awful mess oh well */
3592 camel_header_decode_date(const char *in, int *saveoffset)
3594 const char *inptr = in;
3596 gboolean foundmonth;
3597 int year, offset = 0;
3608 d(printf ("\ndecoding date '%s'\n", inptr));
3610 memset (&tm, 0, sizeof(tm));
3612 header_decode_lwsp (&inptr);
3613 if (!isdigit (*inptr)) {
3614 char *day = decode_token (&inptr);
3615 /* we dont really care about the day, it's only for display */
3617 d(printf ("got day: %s\n", day));
3619 header_decode_lwsp (&inptr);
3620 if (*inptr == ',') {
3624 return parse_broken_date (in, saveoffset);
3629 #endif /* ! CLEAN_DATE */
3633 tm.tm_mday = camel_header_decode_int(&inptr);
3635 if (tm.tm_mday == 0) {
3636 return parse_broken_date (in, saveoffset);
3638 #endif /* ! CLEAN_DATE */
3640 monthname = decode_token(&inptr);
3643 for (i=0;i<sizeof(tz_months)/sizeof(tz_months[0]);i++) {
3644 if (!g_ascii_strcasecmp(tz_months[i], monthname)) {
3654 return parse_broken_date (in, saveoffset);
3656 #endif /* ! CLEAN_DATE */
3658 year = camel_header_decode_int(&inptr);
3660 tm.tm_year = 100 + year;
3661 } else if (year < 100) {
3663 } else if (year >= 100 && year < 1900) {
3666 tm.tm_year = year - 1900;
3668 /* get the time ... yurck */
3669 tm.tm_hour = camel_header_decode_int(&inptr);
3670 header_decode_lwsp(&inptr);
3673 tm.tm_min = camel_header_decode_int(&inptr);
3674 header_decode_lwsp(&inptr);
3677 tm.tm_sec = camel_header_decode_int(&inptr);
3678 header_decode_lwsp(&inptr);
3681 offset = (*inptr++)=='-'?-1:1;
3682 offset = offset * camel_header_decode_int(&inptr);
3683 d(printf("abs signed offset = %d\n", offset));
3684 if (offset < -1200 || offset > 1400)
3686 } else if (isdigit(*inptr)) {
3687 offset = camel_header_decode_int(&inptr);
3688 d(printf("abs offset = %d\n", offset));
3689 if (offset < -1200 || offset > 1400)
3692 char *tz = decode_token(&inptr);
3695 for (i=0;i<sizeof(tz_offsets)/sizeof(tz_offsets[0]);i++) {
3696 if (!g_ascii_strcasecmp(tz_offsets[i].name, tz)) {
3697 offset = tz_offsets[i].offset;
3703 /* some broken mailers seem to put in things like GMT+1030 instead of just +1030 */
3704 header_decode_lwsp(&inptr);
3705 if (*inptr == '+' || *inptr == '-') {
3706 int sign = (*inptr++)=='-'?-1:1;
3707 offset = offset + (camel_header_decode_int(&inptr)*sign);
3709 d(printf("named offset = %d\n", offset));
3712 t = e_mktime_utc(&tm);
3714 /* t is now GMT of the time we want, but not offset by the timezone ... */
3716 d(printf(" gmt normalized? = %s\n", ctime(&t)));
3718 /* this should convert the time to the GMT equiv time */
3719 t -= ( (offset/100) * 60*60) + (offset % 100)*60;
3721 d(printf(" gmt normalized for timezone? = %s\n", ctime(&t)));
3725 tmp = camel_header_format_date(t, offset);
3726 printf(" encoded again: %s\n", tmp);
3731 *saveoffset = offset;
3737 camel_header_location_decode(const char *in)
3740 GString *out = g_string_new("");
3743 /* Sigh. RFC2557 says:
3744 * content-location = "Content-Location:" [CFWS] URI [CFWS]
3745 * where URI is restricted to the syntax for URLs as
3746 * defined in Uniform Resource Locators [URL] until
3747 * IETF specifies other kinds of URIs.
3749 * But Netscape puts quotes around the URI when sending web
3752 * Which is required as defined in rfc2017 [3.1]. Although
3753 * outlook doesn't do this.
3755 * Since we get headers already unfolded, we need just drop
3756 * all whitespace. URL's cannot contain whitespace or quoted
3757 * characters, even when included in quotes.
3760 header_decode_lwsp(&in);
3766 while ( (c = *in++) ) {
3767 if (quote && c=='"')
3769 if (!camel_mime_is_lwsp(c))
3770 g_string_append_c(out, c);
3773 res = g_strdup(out->str);
3774 g_string_free(out, TRUE);
3779 /* extra rfc checks */
3784 check_header(struct _camel_header_raw *h)
3791 w(g_warning("Appending header violates rfc: %s: %s", h->name, h->value));
3800 camel_header_raw_append_parse(struct _camel_header_raw **list, const char *header, int offset)
3802 register const char *in;
3807 while (camel_mime_is_fieldname(*in) || *in==':')
3809 fieldlen = in-header-1;
3810 while (camel_mime_is_lwsp(*in))
3812 if (fieldlen == 0 || header[fieldlen] != ':') {
3813 printf("Invalid header line: '%s'\n", header);
3816 name = g_alloca (fieldlen + 1);
3817 memcpy(name, header, fieldlen);
3820 camel_header_raw_append(list, name, in, offset);
3824 camel_header_raw_append(struct _camel_header_raw **list, const char *name, const char *value, int offset)
3826 struct _camel_header_raw *l, *n;
3828 d(printf("Header: %s: %s\n", name, value));
3830 n = g_malloc(sizeof(*n));
3832 n->name = g_strdup(name);
3833 n->value = g_strdup(value);
3838 l = (struct _camel_header_raw *)list;
3846 if (!g_ascii_strcasecmp(name, "To")) {
3847 printf("- Decoding To\n");
3848 camel_header_to_decode(value);
3849 } else if (!g_ascii_strcasecmp(name, "Content-type")) {
3850 printf("- Decoding content-type\n");
3851 camel_content_type_dump(camel_content_type_decode(value));
3852 } else if (!g_ascii_strcasecmp(name, "MIME-Version")) {
3853 printf("- Decoding mime version\n");
3854 camel_header_mime_decode(value);
3859 static struct _camel_header_raw *
3860 header_raw_find_node(struct _camel_header_raw **list, const char *name)
3862 struct _camel_header_raw *l;
3866 if (!g_ascii_strcasecmp(l->name, name))
3874 camel_header_raw_find(struct _camel_header_raw **list, const char *name, int *offset)
3876 struct _camel_header_raw *l;
3878 l = header_raw_find_node(list, name);
3881 *offset = l->offset;
3888 camel_header_raw_find_next(struct _camel_header_raw **list, const char *name, int *offset, const char *last)
3890 struct _camel_header_raw *l;
3892 if (last == NULL || name == NULL)
3896 while (l && l->value != last)
3898 return camel_header_raw_find(&l, name, offset);
3902 header_raw_free(struct _camel_header_raw *l)
3910 camel_header_raw_remove(struct _camel_header_raw **list, const char *name)
3912 struct _camel_header_raw *l, *p;
3914 /* the next pointer is at the head of the structure, so this is safe */
3915 p = (struct _camel_header_raw *)list;
3918 if (!g_ascii_strcasecmp(l->name, name)) {
3930 camel_header_raw_replace(struct _camel_header_raw **list, const char *name, const char *value, int offset)
3932 camel_header_raw_remove(list, name);
3933 camel_header_raw_append(list, name, value, offset);
3937 camel_header_raw_clear(struct _camel_header_raw **list)
3939 struct _camel_header_raw *l, *n;
3950 camel_header_msgid_generate (void)
3952 static pthread_mutex_t count_lock = PTHREAD_MUTEX_INITIALIZER;
3953 #define COUNT_LOCK() pthread_mutex_lock (&count_lock)
3954 #define COUNT_UNLOCK() pthread_mutex_unlock (&count_lock)
3955 char host[MAXHOSTNAMELEN];
3957 static int count = 0;
3960 struct addrinfo *ai = NULL, hints = { 0 };
3962 retval = gethostname (host, sizeof (host));
3963 if (retval == 0 && *host) {
3964 hints.ai_flags = AI_CANONNAME;
3965 ai = camel_getaddrinfo(host, NULL, &hints, NULL);
3966 if (ai && ai->ai_canonname)
3967 name = ai->ai_canonname;
3971 name = "localhost.localdomain";
3974 msgid = g_strdup_printf ("%d.%d.%d.camel@%s", (int) time (NULL), getpid (), count++, name);
3978 camel_freeaddrinfo(ai);
3988 } mail_list_magic[] = {
3989 /* List-Post: <mailto:gnome-hackers@gnome.org> */
3990 /* List-Post: <mailto:gnome-hackers> */
3991 { "List-Post", "[ \t]*<mailto:([^@>]+)@?([^ \n\t\r>]*)" },
3992 /* List-Id: GNOME stuff <gnome-hackers.gnome.org> */
3993 /* List-Id: <gnome-hackers.gnome.org> */
3994 /* List-Id: <gnome-hackers> */
3995 /* This old one wasn't very useful: { "List-Id", " *([^<]+)" },*/
3996 { "List-Id", "[^<]*<([^\\.>]+)\\.?([^ \n\t\r>]*)" },
3997 /* Mailing-List: list gnome-hackers@gnome.org; contact gnome-hackers-owner@gnome.org */
3998 { "Mailing-List", "[ \t]*list ([^@]+)@?([^ \n\t\r>;]*)" },
3999 /* Originator: gnome-hackers@gnome.org */
4000 { "Originator", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" },
4001 /* X-Mailing-List: <gnome-hackers@gnome.org> arcive/latest/100 */
4002 /* X-Mailing-List: gnome-hackers@gnome.org */
4003 /* X-Mailing-List: gnome-hackers */
4004 /* X-Mailing-List: <gnome-hackers> */
4005 { "X-Mailing-List", "[ \t]*<?([^@>]+)@?([^ \n\t\r>]*)" },
4006 /* X-Loop: gnome-hackers@gnome.org */
4007 { "X-Loop", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" },
4008 /* X-List: gnome-hackers */
4009 /* X-List: gnome-hackers@gnome.org */
4010 { "X-List", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" },
4011 /* Sender: owner-gnome-hackers@gnome.org */
4012 /* Sender: owner-gnome-hacekrs */
4013 { "Sender", "[ \t]*owner-([^@]+)@?([^ @\n\t\r>]*)" },
4014 /* Sender: gnome-hackers-owner@gnome.org */
4015 /* Sender: gnome-hackers-owner */
4016 { "Sender", "[ \t]*([^@]+)-owner@?([^ @\n\t\r>]*)" },
4017 /* Delivered-To: mailing list gnome-hackers@gnome.org */
4018 /* Delivered-To: mailing list gnome-hackers */
4019 { "Delivered-To", "[ \t]*mailing list ([^@]+)@?([^ \n\t\r>]*)" },
4020 /* Sender: owner-gnome-hackers@gnome.org */
4021 /* Sender: <owner-gnome-hackers@gnome.org> */
4022 /* Sender: owner-gnome-hackers */
4023 /* Sender: <owner-gnome-hackers> */
4024 { "Return-Path", "[ \t]*<?owner-([^@>]+)@?([^ \n\t\r>]*)" },
4025 /* X-BeenThere: gnome-hackers@gnome.org */
4026 /* X-BeenThere: gnome-hackers */
4027 { "X-BeenThere", "[ \t]*([^@]+)@?([^ \n\t\r>]*)" },
4028 /* List-Unsubscribe: <mailto:gnome-hackers-unsubscribe@gnome.org> */
4029 { "List-Unsubscribe", "<mailto:(.+)-unsubscribe@([^ \n\t\r>]*)" },
4032 static pthread_once_t mailing_list_init_once = PTHREAD_ONCE_INIT;
4035 mailing_list_init(void)
4037 int i, errcode, failed=0;
4039 /* precompile regex's for speed at runtime */
4040 for (i = 0; i < G_N_ELEMENTS (mail_list_magic); i++) {
4041 errcode = regcomp(&mail_list_magic[i].regex, mail_list_magic[i].pattern, REG_EXTENDED|REG_ICASE);
4046 len = regerror(errcode, &mail_list_magic[i].regex, NULL, 0);
4047 errstr = g_malloc0(len + 1);
4048 regerror(errcode, &mail_list_magic[i].regex, errstr, len);
4050 g_warning("Internal error, compiling regex failed: %s: %s", mail_list_magic[i].pattern, errstr);
4056 g_assert(failed == 0);
4060 camel_header_raw_check_mailing_list(struct _camel_header_raw **list)
4063 regmatch_t match[3];
4066 pthread_once(&mailing_list_init_once, mailing_list_init);
4068 for (i = 0; i < sizeof (mail_list_magic) / sizeof (mail_list_magic[0]); i++) {
4069 v = camel_header_raw_find (list, mail_list_magic[i].name, NULL);
4071 match[j].rm_so = -1;
4072 match[j].rm_eo = -1;
4074 if (v != NULL && regexec (&mail_list_magic[i].regex, v, 3, match, 0) == 0 && match[1].rm_so != -1) {
4078 len1 = match[1].rm_eo - match[1].rm_so;
4079 len2 = match[2].rm_eo - match[2].rm_so;
4081 mlist = g_malloc (len1 + len2 + 2);
4082 memcpy (mlist, v + match[1].rm_so, len1);
4085 memcpy (mlist + len1 + 1, v + match[2].rm_so, len2);
4086 mlist[len1 + len2 + 1] = '\0';
4098 /* ok, here's the address stuff, what a mess ... */
4099 struct _camel_header_address *
4100 camel_header_address_new (void)
4102 struct _camel_header_address *h;
4103 h = g_malloc0(sizeof(*h));
4104 h->type = CAMEL_HEADER_ADDRESS_NONE;
4109 struct _camel_header_address *
4110 camel_header_address_new_name(const char *name, const char *addr)
4112 struct _camel_header_address *h;
4113 h = camel_header_address_new();
4114 h->type = CAMEL_HEADER_ADDRESS_NAME;
4115 h->name = g_strdup(name);
4116 h->v.addr = g_strdup(addr);
4120 struct _camel_header_address *
4121 camel_header_address_new_group (const char *name)
4123 struct _camel_header_address *h;
4125 h = camel_header_address_new();
4126 h->type = CAMEL_HEADER_ADDRESS_GROUP;
4127 h->name = g_strdup(name);
4132 camel_header_address_ref(struct _camel_header_address *h)
4139 camel_header_address_unref(struct _camel_header_address *h)
4142 if (h->refcount <= 1) {
4143 if (h->type == CAMEL_HEADER_ADDRESS_GROUP) {
4144 camel_header_address_list_clear(&h->v.members);
4145 } else if (h->type == CAMEL_HEADER_ADDRESS_NAME) {
4157 camel_header_address_set_name(struct _camel_header_address *h, const char *name)
4161 h->name = g_strdup(name);
4166 camel_header_address_set_addr(struct _camel_header_address *h, const char *addr)
4169 if (h->type == CAMEL_HEADER_ADDRESS_NAME
4170 || h->type == CAMEL_HEADER_ADDRESS_NONE) {
4171 h->type = CAMEL_HEADER_ADDRESS_NAME;
4173 h->v.addr = g_strdup(addr);
4175 g_warning("Trying to set the address on a group");
4181 camel_header_address_set_members(struct _camel_header_address *h, struct _camel_header_address *group)
4184 if (h->type == CAMEL_HEADER_ADDRESS_GROUP
4185 || h->type == CAMEL_HEADER_ADDRESS_NONE) {
4186 h->type = CAMEL_HEADER_ADDRESS_GROUP;
4187 camel_header_address_list_clear(&h->v.members);
4188 /* should this ref them? */
4189 h->v.members = group;
4191 g_warning("Trying to set the members on a name, not group");
4197 camel_header_address_add_member(struct _camel_header_address *h, struct _camel_header_address *member)
4200 if (h->type == CAMEL_HEADER_ADDRESS_GROUP
4201 || h->type == CAMEL_HEADER_ADDRESS_NONE) {
4202 h->type = CAMEL_HEADER_ADDRESS_GROUP;
4203 camel_header_address_list_append(&h->v.members, member);
4209 camel_header_address_list_append_list(struct _camel_header_address **l, struct _camel_header_address **h)
4212 struct _camel_header_address *n = (struct _camel_header_address *)l;
4222 camel_header_address_list_append(struct _camel_header_address **l, struct _camel_header_address *h)
4225 camel_header_address_list_append_list(l, &h);
4231 camel_header_address_list_clear(struct _camel_header_address **l)
4233 struct _camel_header_address *a, *n;
4237 camel_header_address_unref(a);
4243 /* if encode is true, then the result is suitable for mailing, otherwise
4244 the result is suitable for display only (and may not even be re-parsable) */
4246 header_address_list_encode_append (GString *out, int encode, struct _camel_header_address *a)
4252 case CAMEL_HEADER_ADDRESS_NAME:
4254 text = camel_header_encode_phrase (a->name);
4258 g_string_append_printf (out, "%s <%s>", text, a->v.addr);
4260 g_string_append (out, a->v.addr);
4264 case CAMEL_HEADER_ADDRESS_GROUP:
4266 text = camel_header_encode_phrase (a->name);
4269 g_string_append_printf (out, "%s: ", text);
4270 header_address_list_encode_append (out, encode, a->v.members);
4271 g_string_append_printf (out, ";");
4276 g_warning ("Invalid address type");
4281 g_string_append (out, ", ");
4286 camel_header_address_list_encode (struct _camel_header_address *a)
4294 out = g_string_new ("");
4295 header_address_list_encode_append (out, TRUE, a);
4297 g_string_free (out, FALSE);
4303 camel_header_address_list_format (struct _camel_header_address *a)
4311 out = g_string_new ("");
4313 header_address_list_encode_append (out, FALSE, a);
4315 g_string_free (out, FALSE);
4321 camel_header_address_fold (const char *in, size_t headerlen)
4324 const char *inptr = in, *space, *p, *n;
4327 int i, needunfold = FALSE;
4332 /* first, check to see if we even need to fold */
4333 len = headerlen + 2;
4336 n = strchr (p, '\n');
4345 if (len >= CAMEL_FOLD_SIZE)
4350 if (len < CAMEL_FOLD_SIZE)
4351 return g_strdup (in);
4353 /* we need to fold, so first unfold (if we need to), then process */
4355 inptr = in = camel_header_unfold (in);
4357 out = g_string_new ("");
4358 outlen = headerlen + 2;
4360 space = strchr (inptr, ' ');
4362 len = space - inptr + 1;
4364 len = strlen (inptr);
4367 d(printf("next word '%.*s'\n", len, inptr));
4369 if (outlen + len > CAMEL_FOLD_SIZE) {
4370 d(printf("outlen = %d wordlen = %d\n", outlen, len));
4371 /* strip trailing space */
4372 if (out->len > 0 && out->str[out->len-1] == ' ')
4373 g_string_truncate (out, out->len-1);
4374 g_string_append (out, "\n\t");
4379 for (i = 0; i < len; i++) {
4380 g_string_append_c (out, inptr[i]);
4386 g_string_free (out, FALSE);
4389 g_free ((char *)in);
4394 /* simple header folding */
4395 /* will work even if the header is already folded */
4397 camel_header_fold(const char *in, size_t headerlen)
4399 size_t len, outlen, i;
4400 const char *inptr = in, *space, *p, *n;
4403 int needunfold = FALSE;
4408 /* first, check to see if we even need to fold */
4409 len = headerlen + 2;
4412 n = strchr(p, '\n');
4421 if (len >= CAMEL_FOLD_SIZE)
4426 if (len < CAMEL_FOLD_SIZE)
4427 return g_strdup(in);
4429 /* we need to fold, so first unfold (if we need to), then process */
4431 inptr = in = camel_header_unfold(in);
4433 out = g_string_new("");
4434 outlen = headerlen+2;
4436 space = strchr(inptr, ' ');
4438 len = space-inptr+1;
4440 len = strlen(inptr);
4442 d(printf("next word '%.*s'\n", len, inptr));
4443 if (outlen + len > CAMEL_FOLD_SIZE) {
4444 d(printf("outlen = %d wordlen = %d\n", outlen, len));
4445 /* strip trailing space */
4446 if (out->len > 0 && out->str[out->len-1] == ' ')
4447 g_string_truncate(out, out->len-1);
4448 g_string_append(out, "\n\t");
4450 /* check for very long words, just cut them up */
4451 while (outlen+len > CAMEL_FOLD_MAX_SIZE) {
4452 for (i=0;i<CAMEL_FOLD_MAX_SIZE-outlen;i++)
4453 g_string_append_c(out, inptr[i]);
4454 inptr += CAMEL_FOLD_MAX_SIZE-outlen;
4455 len -= CAMEL_FOLD_MAX_SIZE-outlen;
4456 g_string_append(out, "\n\t");
4461 for (i=0;i<len;i++) {
4462 g_string_append_c(out, inptr[i]);
4467 g_string_free(out, FALSE);
4476 camel_header_unfold(const char *in)
4478 char *out = g_malloc(strlen(in)+1);
4479 const char *inptr = in;
4483 while ((c = *inptr++)) {
4485 if (camel_mime_is_lwsp(*inptr)) {
4488 } while (camel_mime_is_lwsp(*inptr));