Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-mime-utils.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Copyright (C) 2000-2003 Ximian Inc.
4  *
5  *  Authors: Michael Zucchi <notzed@ximian.com>
6  *           Jeffrey Stedfast <fejj@ximian.com>
7  *
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.
11  *
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.
16  *
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.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 /* POSIX requires <sys/types.h> be included before <regex.h> */
28 #include <sys/types.h>
29
30 #include <ctype.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <pthread.h>
34 #include <regex.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <time.h>
39 #include <unistd.h>
40 #include <sys/param.h>  /* for MAXHOSTNAMELEN */
41 #include <sys/stat.h>
42
43 #ifndef MAXHOSTNAMELEN
44 #define MAXHOSTNAMELEN 1024
45 #endif
46
47 #include <glib.h>
48
49 #include <libedataserver/e-iconv.h>
50 #include <libedataserver/e-time-utils.h>
51
52 #include "camel-charset-map.h"
53 #include "camel-mime-utils.h"
54 #include "camel-net-utils.h"
55 #include "camel-utf8.h"
56
57 #ifndef CLEAN_DATE
58 #include "broken-date-parser.h"
59 #endif
60
61 #ifdef G_OS_WIN32
62 /* Undef the similar macro from pthread.h, it doesn't check if
63  * gmtime() returns NULL.
64  */
65 #undef gmtime_r
66
67 /* The gmtime() in Microsoft's C library is MT-safe */
68 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
69 #endif
70
71 #if 0
72 int strdup_count = 0;
73 int malloc_count = 0;
74 int free_count = 0;
75
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))
79 #endif
80
81 /* for all non-essential warnings ... */
82 #define w(x)
83
84 #define d(x)
85 #define d2(x)
86
87 #define CAMEL_UUENCODE_CHAR(c)  ((c) ? (c) + ' ' : '`')
88 #define CAMEL_UUDECODE_CHAR(c)  (((c) - ' ') & 077)
89
90 static const char base64_alphabet[] =
91 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
92
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'
96 };
97
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,
131 };
132
133
134 /**
135  * camel_base64_encode_close:
136  * @in: input stream
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
142  *
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.
146  *
147  * Returns the number of bytes encoded
148  **/
149 size_t
150 camel_base64_encode_close(unsigned char *in, size_t inlen, gboolean break_lines, unsigned char *out, int *state, int *save)
151 {
152         int c1, c2;
153         unsigned char *outptr = out;
154
155         if (inlen>0)
156                 outptr += camel_base64_encode_step(in, inlen, break_lines, outptr, state, save);
157
158         c1 = ((unsigned char *)save)[1];
159         c2 = ((unsigned char *)save)[2];
160         
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]));
165
166         switch (((char *)save)[0]) {
167         case 2:
168                 outptr[2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ];
169                 g_assert(outptr[2] != 0);
170                 goto skip;
171         case 1:
172                 outptr[2] = '=';
173         skip:
174                 outptr[0] = base64_alphabet[ c1 >> 2 ];
175                 outptr[1] = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 )];
176                 outptr[3] = '=';
177                 outptr += 4;
178                 break;
179         }
180         if (break_lines)
181                 *outptr++ = '\n';
182
183         *save = 0;
184         *state = 0;
185
186         return outptr-out;
187 }
188
189
190 /**
191  * camel_base64_encode_step:
192  * @in: input stream
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
198  *
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
202  * invocation).
203  *
204  * Returns the number of bytes encoded
205  **/
206 size_t
207 camel_base64_encode_step(unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save)
208 {
209         register unsigned char *inptr, *outptr;
210
211         if (len<=0)
212                 return 0;
213
214         inptr = in;
215         outptr = out;
216
217         d(printf("we have %d chars, and %d saved chars\n", len, ((char *)save)[0]));
218
219         if (len + ((char *)save)[0] > 2) {
220                 unsigned char *inend = in+len-2;
221                 register int c1, c2, c3;
222                 register int already;
223
224                 already = *state;
225
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;
230                 }
231                 
232                 /* yes, we jump into the loop, no i'm not going to change it, it's beautiful! */
233                 while (inptr < inend) {
234                         c1 = *inptr++;
235                 skip1:
236                         c2 = *inptr++;
237                 skip2:
238                         c3 = *inptr++;
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) {
245                                 *outptr++='\n';
246                                 already = 0;
247                         }
248                 }
249
250                 ((char *)save)[0] = 0;
251                 len = 2-(inptr-inend);
252                 *state = already;
253         }
254
255         d(printf("state = %d, len = %d\n",
256                  (int)((char *)save)[0],
257                  len));
258
259         if (len>0) {
260                 register char *saveout;
261
262                 /* points to the slot for the next char to save */
263                 saveout = & (((char *)save)[1]) + ((char *)save)[0];
264
265                 /* len can only be 0 1 or 2 */
266                 switch(len) {
267                 case 2: *saveout++ = *inptr++;
268                 case 1: *saveout++ = *inptr++;
269                 }
270                 ((char *)save)[0]+=len;
271         }
272
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]));
277
278         return outptr-out;
279 }
280
281
282 /**
283  * camel_base64_decode_step: decode a chunk of base64 encoded data
284  * @in: input stream
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
289  *
290  * Decodes a chunk of base64 encoded data
291  *
292  * Returns the number of bytes decoded (which have been dumped in @out)
293  **/
294 size_t
295 camel_base64_decode_step(unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save)
296 {
297         register unsigned char *inptr, *outptr;
298         unsigned char *inend, c;
299         register unsigned int v;
300         int i;
301
302         inend = in+len;
303         outptr = out;
304
305         /* convert 4 base64 bytes to 3 normal bytes */
306         v=*save;
307         i=*state;
308         inptr = in;
309         while (inptr<inend) {
310                 c = camel_mime_base64_rank[*inptr++];
311                 if (c != 0xff) {
312                         v = (v<<6) | c;
313                         i++;
314                         if (i==4) {
315                                 *outptr++ = v>>16;
316                                 *outptr++ = v>>8;
317                                 *outptr++ = v;
318                                 i=0;
319                         }
320                 }
321         }
322
323         *save = v;
324         *state = i;
325
326         /* quick scan back for '=' on the end somewhere */
327         /* fortunately we can drop 1 output char for each trailing = (upto 2) */
328         i=2;
329         while (inptr>in && i) {
330                 inptr--;
331                 if (camel_mime_base64_rank[*inptr] != 0xff) {
332                         if (*inptr == '=' && outptr>out)
333                                 outptr--;
334                         i--;
335                 }
336         }
337
338         /* if i!= 0 then there is a truncation error! */
339         return outptr-out;
340 }
341
342
343 /**
344  * camel_base64_encode_simple:
345  * @data: binary stream of data to encode
346  * @len: length of data
347  *
348  * Base64 encodes a block of memory.
349  *
350  * Returns a string containing the base64 encoded data
351  **/
352 char *
353 camel_base64_encode_simple (const char *data, size_t len)
354 {
355         unsigned char *out;
356         int state = 0, outlen;
357         unsigned int save = 0;
358         
359         out = g_malloc (len * 4 / 3 + 5);
360         outlen = camel_base64_encode_close ((unsigned char *)data, len, FALSE,
361                                       out, &state, &save);
362         out[outlen] = '\0';
363         return (char *)out;
364 }
365
366
367 /**
368  * camel_base64_decode_simple:
369  * @data: data to decode
370  * @len: length of data
371  *
372  * Base64 decodes @data inline (overwrites @data with the decoded version).
373  *
374  * Returns the new length of @data
375  **/
376 size_t
377 camel_base64_decode_simple (char *data, size_t len)
378 {
379         int state = 0;
380         unsigned int save = 0;
381
382         return camel_base64_decode_step ((unsigned char *)data, len,
383                                    (unsigned char *)data, &state, &save);
384 }
385
386 /**
387  * camel_uuencode_close:
388  * @in: input stream
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
394  *
395  * Uuencodes a chunk of data. Call this when finished encoding data
396  * with #camel_uuencode_step to flush off the last little bit.
397  *
398  * Returns the number of bytes encoded
399  **/
400 size_t
401 camel_uuencode_close (unsigned char *in, size_t len, unsigned char *out, unsigned char *uubuf, int *state, guint32 *save)
402 {
403         register unsigned char *outptr, *bufptr;
404         register guint32 saved;
405         int uulen, uufill, i;
406         
407         outptr = out;
408         
409         if (len > 0)
410                 outptr += camel_uuencode_step (in, len, out, uubuf, state, save);
411         
412         uufill = 0;
413         
414         saved = *save;
415         i = *state & 0xff;
416         uulen = (*state >> 8) & 0xff;
417         
418         bufptr = uubuf + ((uulen / 3) * 4);
419         
420         if (i > 0) {
421                 while (i < 3) {
422                         saved <<= 8 | 0;
423                         uufill++;
424                         i++;
425                 }
426                 
427                 if (i == 3) {
428                         /* convert 3 normal bytes into 4 uuencoded bytes */
429                         unsigned char b0, b1, b2;
430                         
431                         b0 = saved >> 16;
432                         b1 = saved >> 8 & 0xff;
433                         b2 = saved & 0xff;
434                         
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);
439                         
440                         i = 0;
441                         saved = 0;
442                         uulen += 3;
443                 }
444         }
445         
446         if (uulen > 0) {
447                 int cplen = ((uulen / 3) * 4);
448                 
449                 *outptr++ = CAMEL_UUENCODE_CHAR ((uulen - uufill) & 0xff);
450                 memcpy (outptr, uubuf, cplen);
451                 outptr += cplen;
452                 *outptr++ = '\n';
453                 uulen = 0;
454         }
455         
456         *outptr++ = CAMEL_UUENCODE_CHAR (uulen & 0xff);
457         *outptr++ = '\n';
458         
459         *save = 0;
460         *state = 0;
461         
462         return outptr - out;
463 }
464
465
466 /**
467  * camel_uuencode_step:
468  * @in: input stream
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
474  *
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
478  * invocation).
479  *
480  * Returns the number of bytes encoded
481  **/
482 size_t
483 camel_uuencode_step (unsigned char *in, size_t len, unsigned char *out, unsigned char *uubuf, int *state, guint32 *save)
484 {
485         register unsigned char *inptr, *outptr, *bufptr;
486         unsigned char *inend;
487         register guint32 saved;
488         int uulen, i;
489         
490         saved = *save;
491         i = *state & 0xff;
492         uulen = (*state >> 8) & 0xff;
493         
494         inptr = in;
495         inend = in + len;
496         
497         outptr = out;
498         
499         bufptr = uubuf + ((uulen / 3) * 4);
500         
501         while (inptr < inend) {
502                 while (uulen < 45 && inptr < inend) {
503                         while (i < 3 && inptr < inend) {
504                                 saved = (saved << 8) | *inptr++;
505                                 i++;
506                         }
507                         
508                         if (i == 3) {
509                                 /* convert 3 normal bytes into 4 uuencoded bytes */
510                                 unsigned char b0, b1, b2;
511                                 
512                                 b0 = saved >> 16;
513                                 b1 = saved >> 8 & 0xff;
514                                 b2 = saved & 0xff;
515                                 
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);
520                                 
521                                 i = 0;
522                                 saved = 0;
523                                 uulen += 3;
524                         }
525                 }
526                 
527                 if (uulen >= 45) {
528                         *outptr++ = CAMEL_UUENCODE_CHAR (uulen & 0xff);
529                         memcpy (outptr, uubuf, ((uulen / 3) * 4));
530                         outptr += ((uulen / 3) * 4);
531                         *outptr++ = '\n';
532                         uulen = 0;
533                         bufptr = uubuf;
534                 }
535         }
536         
537         *save = saved;
538         *state = ((uulen & 0xff) << 8) | (i & 0xff);
539         
540         return outptr - out;
541 }
542
543
544 /**
545  * camel_uudecode_step:
546  * @in: input stream
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
551  *
552  * Uudecodes a chunk of data. Performs a 'decode step' on a chunk of
553  * uuencoded data. Assumes the "begin mode filename" line has
554  * been stripped off.
555  *
556  * Returns the number of bytes decoded
557  **/
558 size_t
559 camel_uudecode_step (unsigned char *in, size_t len, unsigned char *out, int *state, guint32 *save)
560 {
561         register unsigned char *inptr, *outptr;
562         unsigned char *inend, ch;
563         register guint32 saved;
564         gboolean last_was_eoln;
565         int uulen, i;
566         
567         if (*state & CAMEL_UUDECODE_STATE_END)
568                 return 0;
569         
570         saved = *save;
571         i = *state & 0xff;
572         uulen = (*state >> 8) & 0xff;
573         if (uulen == 0)
574                 last_was_eoln = TRUE;
575         else
576                 last_was_eoln = FALSE;
577         
578         inend = in + len;
579         outptr = out;
580         
581         inptr = in;
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;
587                                 if (uulen == 0) {
588                                         *state |= CAMEL_UUDECODE_STATE_END;
589                                         break;
590                                 }
591                         } else {
592                                 last_was_eoln = TRUE;
593                         }
594                         
595                         inptr++;
596                         continue;
597                 }
598                 
599                 ch = *inptr++;
600                 
601                 if (uulen > 0) {
602                         /* save the byte */
603                         saved = (saved << 8) | ch;
604                         i++;
605                         if (i == 4) {
606                                 /* convert 4 uuencoded bytes to 3 normal bytes */
607                                 unsigned char b0, b1, b2, b3;
608                                 
609                                 b0 = saved >> 24;
610                                 b1 = saved >> 16 & 0xff;
611                                 b2 = saved >> 8 & 0xff;
612                                 b3 = saved & 0xff;
613                                 
614                                 if (uulen >= 3) {
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);
618                                 } else {
619                                         if (uulen >= 1) {
620                                                 *outptr++ = CAMEL_UUDECODE_CHAR (b0) << 2 | CAMEL_UUDECODE_CHAR (b1) >> 4;
621                                         }
622                                         if (uulen >= 2) {
623                                                 *outptr++ = CAMEL_UUDECODE_CHAR (b1) << 4 | CAMEL_UUDECODE_CHAR (b2) >> 2;
624                                         }
625                                 }
626                                 
627                                 i = 0;
628                                 saved = 0;
629                                 uulen -= 3;
630                         }
631                 } else {
632                         break;
633                 }
634         }
635         
636         *save = saved;
637         *state = (*state & CAMEL_UUDECODE_STATE_MASK) | ((uulen & 0xff) << 8) | (i & 0xff);
638         
639         return outptr - out;
640 }
641
642
643 /**
644  * camel_quoted_encode_close:
645  * @in: input stream
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
650  *
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.
654  *
655  * Returns the number of bytes encoded
656  **/
657 size_t
658 camel_quoted_encode_close(unsigned char *in, size_t len, unsigned char *out, int *state, int *save)
659 {
660         register unsigned char *outptr = out;
661         int last;
662
663         if (len>0)
664                 outptr += camel_quoted_encode_step(in, len, outptr, state, save);
665
666         last = *state;
667         if (last != -1) {
668                 /* space/tab must be encoded if it's the last character on
669                    the line */
670                 if (camel_mime_is_qpsafe(last) && last!=' ' && last!=9) {
671                         *outptr++ = last;
672                 } else {
673                         *outptr++ = '=';
674                         *outptr++ = tohex[(last>>4) & 0xf];
675                         *outptr++ = tohex[last & 0xf];
676                 }
677         }
678
679         *save = 0;
680         *state = -1;
681
682         return outptr-out;
683 }
684
685
686 /**
687  * camel_quoted_encode_step:
688  * @in: input stream
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
693  *
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
696  * first invocation).
697  *
698  * Returns the number of bytes encoded
699  **/
700 size_t
701 camel_quoted_encode_step (unsigned char *in, size_t len, unsigned char *out, int *statep, int *save)
702 {
703         register guchar *inptr, *outptr, *inend;
704         unsigned char c;
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 */
707         
708         inptr = in;
709         inend = in + len;
710         outptr = out;
711         while (inptr < inend) {
712                 c = *inptr++;
713                 if (c == '\r') {
714                         if (last != -1) {
715                                 *outptr++ = '=';
716                                 *outptr++ = tohex[(last >> 4) & 0xf];
717                                 *outptr++ = tohex[last & 0xf];
718                                 sofar += 3;
719                         }
720                         last = c;
721                 } else if (c == '\n') {
722                         if (last != -1 && last != '\r') {
723                                 *outptr++ = '=';
724                                 *outptr++ = tohex[(last >> 4) & 0xf];
725                                 *outptr++ = tohex[last & 0xf];
726                         }
727                         *outptr++ = '\n';
728                         sofar = 0;
729                         last = -1;
730                 } else {
731                         if (last != -1) {
732                                 if (camel_mime_is_qpsafe(last)) {
733                                         *outptr++ = last;
734                                         sofar++;
735                                 } else {
736                                         *outptr++ = '=';
737                                         *outptr++ = tohex[(last >> 4) & 0xf];
738                                         *outptr++ = tohex[last & 0xf];
739                                         sofar += 3;
740                                 }
741                         }
742                         
743                         if (camel_mime_is_qpsafe(c)) {
744                                 if (sofar > 74) {
745                                         *outptr++ = '=';
746                                         *outptr++ = '\n';
747                                         sofar = 0;
748                                 }
749                                 
750                                 /* delay output of space char */
751                                 if (c==' ' || c=='\t') {
752                                         last = c;
753                                 } else {
754                                         *outptr++ = c;
755                                         sofar++;
756                                         last = -1;
757                                 }
758                         } else {
759                                 if (sofar > 72) {
760                                         *outptr++ = '=';
761                                         *outptr++ = '\n';
762                                         sofar = 3;
763                                 } else
764                                         sofar += 3;
765                                 
766                                 *outptr++ = '=';
767                                 *outptr++ = tohex[(c >> 4) & 0xf];
768                                 *outptr++ = tohex[c & 0xf];
769                                 last = -1;
770                         }
771                 }
772         }
773         *save = sofar;
774         *statep = last;
775         
776         return (outptr - out);
777 }
778
779 /*
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??
782
783   Note: Trailing rubbish (at the end of input), like = or =x or =\r will be lost.
784 */ 
785
786 /**
787  * camel_quoted_decode_step:
788  * @in: input stream
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
793  *
794  * Decodes a block of quoted-printable encoded data. Performs a
795  * 'decode step' on a chunk of QP encoded data.
796  *
797  * Returns the number of bytes decoded
798  **/
799 size_t
800 camel_quoted_decode_step(unsigned char *in, size_t len, unsigned char *out, int *savestate, int *saveme)
801 {
802         register unsigned char *inptr, *outptr;
803         unsigned char *inend, c;
804         int state, save;
805
806         inend = in+len;
807         outptr = out;
808
809         d(printf("quoted-printable, decoding text '%.*s'\n", len, in));
810
811         state = *savestate;
812         save = *saveme;
813         inptr = in;
814         while (inptr<inend) {
815                 switch (state) {
816                 case 0:
817                         while (inptr<inend) {
818                                 c = *inptr++;
819                                 if (c=='=') { 
820                                         state = 1;
821                                         break;
822                                 }
823 #ifdef CANONICALISE_EOL
824                                 /*else if (c=='\r') {
825                                         state = 3;
826                                 } else if (c=='\n') {
827                                         *outptr++ = '\r';
828                                         *outptr++ = c;
829                                         } */
830 #endif
831                                 else {
832                                         *outptr++ = c;
833                                 }
834                         }
835                         break;
836                 case 1:
837                         c = *inptr++;
838                         if (c=='\n') {
839                                 /* soft break ... unix end of line */
840                                 state = 0;
841                         } else {
842                                 save = c;
843                                 state = 2;
844                         }
845                         break;
846                 case 2:
847                         c = *inptr++;
848                         if (isxdigit(c) && isxdigit(save)) {
849                                 c = toupper(c);
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 */
855                         } else {
856                                 /* just output the data */
857                                 *outptr++ = '=';
858                                 *outptr++ = save;
859                                 *outptr++ = c;
860                         }
861                         state = 0;
862                         break;
863 #ifdef CANONICALISE_EOL
864                 case 3:
865                         /* convert \r -> to \r\n, leaves \r\n alone */
866                         c = *inptr++;
867                         if (c=='\n') {
868                                 *outptr++ = '\r';
869                                 *outptr++ = c;
870                         } else {
871                                 *outptr++ = '\r';
872                                 *outptr++ = '\n';
873                                 *outptr++ = c;
874                         }
875                         state = 0;
876                         break;
877 #endif
878                 }
879         }
880
881         *savestate = state;
882         *saveme = save;
883
884         return outptr-out;
885 }
886
887 /*
888   this is for the "Q" encoding of international words,
889   which is slightly different than plain quoted-printable (mainly by allowing 0x20 <> _)
890 */
891 static size_t
892 quoted_decode(const unsigned char *in, size_t len, unsigned char *out)
893 {
894         register const unsigned char *inptr;
895         register unsigned char *outptr;
896         unsigned const char *inend;
897         unsigned char c, c1;
898         int ret = 0;
899
900         inend = in+len;
901         outptr = out;
902
903         d(printf("decoding text '%.*s'\n", len, in));
904
905         inptr = in;
906         while (inptr<inend) {
907                 c = *inptr++;
908                 if (c=='=') {
909                         /* silently ignore truncated data? */
910                         if (inend-in>=2) {
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);
915                         } else {
916                                 ret = -1;
917                                 break;
918                         }
919                 } else if (c=='_') {
920                         *outptr++ = 0x20;
921                 } else if (c==' ' || c==0x09) {
922                         /* FIXME: this is an error! ignore for now ... */
923                         ret = -1;
924                         break;
925                 } else {
926                         *outptr++ = c;
927                 }
928         }
929         if (ret==0) {
930                 return outptr-out;
931         }
932         return 0;
933 }
934
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 */
938 static size_t
939 quoted_encode (const unsigned char *in, size_t len, unsigned char *out, unsigned short safemask)
940 {
941         register const unsigned char *inptr, *inend;
942         unsigned char *outptr;
943         unsigned char c;
944
945         inptr = in;
946         inend = in + len;
947         outptr = out;
948         while (inptr < inend) {
949                 c = *inptr++;
950                 if (c==' ') {
951                         *outptr++ = '_';
952                 } else if (camel_mime_special_table[c] & safemask) {
953                         *outptr++ = c;
954                 } else {
955                         *outptr++ = '=';
956                         *outptr++ = tohex[(c >> 4) & 0xf];
957                         *outptr++ = tohex[c & 0xf];
958                 }
959         }
960
961         d(printf("encoding '%.*s' = '%.*s'\n", len, in, outptr-out, out));
962
963         return (outptr - out);
964 }
965
966
967 static void
968 header_decode_lwsp(const char **in)
969 {
970         const char *inptr = *in;
971         char c;
972
973         d2(printf("is ws: '%s'\n", *in));
974
975         while ((camel_mime_is_lwsp(*inptr) || *inptr =='(') && *inptr != '\0') {
976                 while (camel_mime_is_lwsp(*inptr) && *inptr != '\0') {
977                         d2(printf("(%c)", *inptr));
978                         inptr++;
979                 }
980                 d2(printf("\n"));
981
982                 /* check for comments */
983                 if (*inptr == '(') {
984                         int depth = 1;
985                         inptr++;
986                         while (depth && (c=*inptr) && *inptr != '\0') {
987                                 if (c=='\\' && inptr[1]) {
988                                         inptr++;
989                                 } else if (c=='(') {
990                                         depth++;
991                                 } else if (c==')') {
992                                         depth--;
993                                 }
994                                 inptr++;
995                         }
996                 }
997         }
998         *in = inptr;
999 }
1000
1001 /* decode rfc 2047 encoded string segment */
1002 static char *
1003 rfc2047_decode_word(const char *in, size_t len)
1004 {
1005         const char *inptr = in+2;
1006         const char *inend = in+len-2;
1007         const char *inbuf;
1008         const char *charset;
1009         char *encname, *p;
1010         int tmplen;
1011         size_t ret;
1012         char *decword = NULL;
1013         char *decoded = NULL;
1014         char *outbase = NULL;
1015         char *outbuf;
1016         size_t inlen, outlen;
1017         gboolean retried = FALSE;
1018         iconv_t ic;
1019         
1020         d(printf("rfc2047: decoding '%.*s'\n", len, in));
1021
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"));
1025                 return NULL;
1026         }
1027         
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]));
1032                 inptr++;
1033                 tmplen = inend-inptr-2;
1034                 decword = g_alloca (tmplen); /* this will always be more-than-enough room */
1035                 switch(toupper(inptr[0])) {
1036                 case 'Q':
1037                         inlen = quoted_decode(inptr+2, tmplen, decword);
1038                         break;
1039                 case 'B': {
1040                         int state = 0;
1041                         unsigned int save = 0;
1042                         
1043                         inlen = camel_base64_decode_step((char *)inptr+2, tmplen, decword, &state, &save);
1044                         /* if state != 0 then error? */
1045                         break;
1046                 }
1047                 default:
1048                         /* uhhh, unknown encoding type - probably an invalid encoded word string */
1049                         return NULL;
1050                 }
1051                 d(printf("The encoded length = %d\n", inlen));
1052                 if (inlen > 0) {
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';
1058                         
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 "?="
1064                          */
1065                         
1066                         /* trim off the 'language' part if it's there... */
1067                         p = strchr (encname, '*');
1068                         if (p)
1069                                 *p = '\0';
1070                         
1071                         charset = e_iconv_charset_name (encname);
1072                         
1073                         inbuf = decword;
1074                         
1075                         outlen = inlen * 6 + 16;
1076                         outbase = g_alloca (outlen);
1077                         outbuf = outbase;
1078                         
1079                 retry:
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);
1085                                         *outbuf = 0;
1086                                         decoded = g_strdup (outbase);
1087                                 }
1088                                 e_iconv_close (ic);
1089                         } else {
1090                                 w(g_warning ("Cannot decode charset, header display may be corrupt: %s: %s",
1091                                              charset, strerror (errno)));
1092                                 
1093                                 if (!retried) {
1094                                         charset = e_iconv_locale_charset ();
1095                                         if (!charset)
1096                                                 charset = "iso-8859-1";
1097                                         
1098                                         retried = TRUE;
1099                                         goto retry;
1100                                 }
1101                                 
1102                                 /* we return the encoded word here because we've got to return valid utf8 */
1103                                 decoded = g_strndup (in, inlen);
1104                         }
1105                 }
1106         }
1107         
1108         d(printf("decoded '%s'\n", decoded));
1109         
1110         return decoded;
1111 }
1112
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 */
1117 static GString *
1118 append_latin1 (GString *out, const char *in, size_t len)
1119 {
1120         unsigned int c;
1121         
1122         while (len) {
1123                 c = (unsigned int)*in++;
1124                 len--;
1125                 if (c & 0x80) {
1126                         out = g_string_append_c (out, 0xc0 | ((c >> 6) & 0x3));  /* 110000xx */
1127                         out = g_string_append_c (out, 0x80 | (c & 0x3f));        /* 10xxxxxx */
1128                 } else {
1129                         out = g_string_append_c (out, c);
1130                 }
1131         }
1132         return out;
1133 }
1134
1135 static int
1136 append_8bit (GString *out, const char *inbuf, size_t inlen, const char *charset)
1137 {
1138         char *outbase, *outbuf;
1139         size_t outlen;
1140         iconv_t ic;
1141         
1142         ic = e_iconv_open ("UTF-8", charset);
1143         if (ic == (iconv_t) -1)
1144                 return FALSE;
1145
1146         outlen = inlen * 6 + 16;
1147         outbuf = outbase = g_malloc(outlen);
1148         
1149         if (e_iconv (ic, &inbuf, &inlen, &outbuf, &outlen) == (size_t) -1) {
1150                 w(g_warning("Conversion to '%s' failed: %s", charset, strerror (errno)));
1151                 g_free(outbase);
1152                 e_iconv_close (ic);
1153                 return FALSE;
1154         }
1155         
1156         e_iconv (ic, NULL, NULL, &outbuf, &outlen);
1157         
1158         *outbuf = 0;
1159         g_string_append(out, outbase);
1160         g_free(outbase);
1161         e_iconv_close (ic);
1162
1163         return TRUE;
1164         
1165 }
1166
1167 static GString *
1168 append_quoted_pair (GString *str, const char *in, gssize inlen)
1169 {
1170         register const char *inptr = in;
1171         const char *inend = in + inlen;
1172         char c;
1173         
1174         while (inptr < inend) {
1175                 c = *inptr++;
1176                 if (c == '\\' && inptr < inend)
1177                         g_string_append_c (str, *inptr++);
1178                 else
1179                         g_string_append_c (str, c);
1180         }
1181
1182         return str;
1183 }
1184
1185 /* decodes a simple text, rfc822 + rfc2047 */
1186 static char *
1187 header_decode_text (const char *in, size_t inlen, int ctext, const char *default_charset)
1188 {
1189         GString *out;
1190         const char *inptr, *inend, *start, *chunk, *locale_charset;
1191         GString *(* append) (GString *, const char *, gssize);
1192         char *dword = NULL;
1193         guint32 mask;
1194         
1195         locale_charset = e_iconv_locale_charset ();
1196         
1197         if (ctext) {
1198                 mask = (CAMEL_MIME_IS_SPECIAL | CAMEL_MIME_IS_SPACE | CAMEL_MIME_IS_CTRL);
1199                 append = append_quoted_pair;
1200         } else {
1201                 mask = (CAMEL_MIME_IS_LWSP);
1202                 append = g_string_append_len;
1203         }
1204         
1205         out = g_string_new ("");
1206         inptr = in;
1207         inend = inptr + inlen;
1208         chunk = NULL;
1209
1210         while (inptr < inend) {
1211                 start = inptr;
1212                 while (inptr < inend && camel_mime_is_type (*inptr, mask))
1213                         inptr++;
1214
1215                 if (inptr == inend) {
1216                         append (out, start, inptr - start);
1217                         break;
1218                 } else if (dword == NULL) {
1219                         append (out, start, inptr - start);
1220                 } else {
1221                         chunk = start;
1222                 }
1223
1224                 start = inptr;
1225                 while (inptr < inend && !camel_mime_is_type (*inptr, mask))
1226                         inptr++;
1227
1228                 dword = rfc2047_decode_word(start, inptr-start);
1229                 if (dword) {
1230                         g_string_append(out, dword);
1231                         g_free(dword);
1232                 } else {
1233                         if (!chunk)
1234                                 chunk = start;
1235                         
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);
1239                 }
1240                 
1241                 chunk = NULL;
1242         }
1243
1244         dword = out->str;
1245         g_string_free (out, FALSE);
1246         
1247         return dword;
1248 }
1249
1250
1251 /**
1252  * camel_header_decode_string:
1253  * @in: input header value string
1254  * @default_charset: default charset to use if improperly encoded
1255  *
1256  * Decodes rfc2047 encoded-word tokens
1257  *
1258  * Returns a string containing the UTF-8 version of the decoded header
1259  * value
1260  **/
1261 char *
1262 camel_header_decode_string (const char *in, const char *default_charset)
1263 {
1264         if (in == NULL)
1265                 return NULL;
1266         return header_decode_text (in, strlen (in), FALSE, default_charset);
1267 }
1268
1269
1270 /**
1271  * camel_header_format_ctext:
1272  * @in: input header value string
1273  * @default_charset: default charset to use if improperly encoded
1274  *
1275  * Decodes a header which contains rfc2047 encoded-word tokens that
1276  * may or may not be within a comment.
1277  *
1278  * Returns a string containing the UTF-8 version of the decoded header
1279  * value
1280  **/
1281 char *
1282 camel_header_format_ctext (const char *in, const char *default_charset)
1283 {
1284         if (in == NULL)
1285                 return NULL;
1286         return header_decode_text (in, strlen (in), TRUE, default_charset);
1287 }
1288
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)
1292
1293 /* FIXME: needs a way to cache iconv opens for different charsets? */
1294 static void
1295 rfc2047_encode_word(GString *outstring, const char *in, size_t len, const char *type, unsigned short safemask)
1296 {
1297         iconv_t ic = (iconv_t) -1;
1298         char *buffer, *out, *ascii;
1299         size_t inlen, outlen, enclen, bufflen;
1300         const char *inptr, *p;
1301         int first = 1;
1302
1303         d(printf("Converting [%d] '%.*s' to %s\n", len, len, in, type));
1304
1305         /* convert utf8->encoding */
1306         bufflen = len * 6 + 16;
1307         buffer = g_alloca (bufflen);
1308         inlen = len;
1309         inptr = in;
1310         
1311         ascii = g_alloca (bufflen);
1312         
1313         if (g_ascii_strcasecmp (type, "UTF-8") != 0)
1314                 ic = e_iconv_open (type, "UTF-8");
1315         
1316         while (inlen) {
1317                 size_t convlen, proclen;
1318                 int i;
1319                 
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 */
1322
1323                 out = buffer;
1324                 outlen = bufflen;
1325
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 */
1331                         convlen = 0;
1332                         proclen = -1;
1333                         p = inptr;
1334                         i = 0;
1335                         while (p < (in+len) && convlen < (75 - strlen("=?utf-8?q?\?="))) {
1336                                 unsigned char c = *p++;
1337
1338                                 if (c >= 0xc0)
1339                                         proclen = i;
1340                                 i++;
1341                                 if (c < 0x80)
1342                                         proclen = i;
1343                                 if (camel_mime_special_table[c] & safemask)
1344                                         convlen += 1;
1345                                 else
1346                                         convlen += 3;
1347                         }
1348                         if (proclen >= 0 && proclen < i && convlen < (75 - strlen("=?utf-8?q?\?=")))
1349                                 proclen = i;
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"));
1353                                 proclen = inlen;
1354                         }
1355                         memcpy(out, inptr, proclen);
1356                         inptr += proclen;
1357                         inlen -= proclen;
1358                         out += proclen;
1359                 } else {
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);
1363                         p = inptr;
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 ... */
1367                                 inptr += convlen;
1368                         } else {
1369                                 /* make sure we flush out any shift state */
1370                                 e_iconv (ic, NULL, 0, &out, &outlen);
1371                         }
1372                         inlen -= (inptr - p);
1373                 }
1374                 
1375                 enclen = out-buffer;
1376                 
1377                 if (enclen) {
1378                         /* create token */
1379                         out = ascii;
1380                         if (first)
1381                                 first = 0;
1382                         else
1383                                 *out++ = ' ';
1384                         out += sprintf (out, "=?%s?Q?", type);
1385                         out += quoted_encode (buffer, enclen, out, safemask);
1386                         sprintf (out, "?=");
1387                         
1388                         d(printf("converted part = %s\n", ascii));
1389                         
1390                         g_string_append (outstring, ascii);
1391                 }
1392         }
1393         
1394         if (ic != (iconv_t) -1)
1395                 e_iconv_close (ic);
1396 }
1397
1398
1399 /* TODO: Should this worry about quotes?? */
1400 /**
1401  * camel_header_encode_string:
1402  * @in: input string
1403  *
1404  * Encodes a 'text' header according to the rules of rfc2047.
1405  *
1406  * Returns the rfc2047 encoded header
1407  **/
1408 char *
1409 camel_header_encode_string (const unsigned char *in)
1410 {
1411         const unsigned char *inptr = in, *start, *word;
1412         gboolean last_was_encoded = FALSE;
1413         gboolean last_was_space = FALSE;
1414         const char *charset;
1415         int encoding;
1416         GString *out;
1417         char *outstr;
1418
1419         g_return_val_if_fail (g_utf8_validate (in, -1, NULL), NULL);
1420         
1421         if (in == NULL)
1422                 return NULL;
1423         
1424         /* do a quick us-ascii check (the common case?) */
1425         while (*inptr) {
1426                 if (*inptr > 127)
1427                         break;
1428                 inptr++;
1429         }
1430         if (*inptr == '\0')
1431                 return g_strdup (in);
1432         
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 ("");
1437         inptr = in;
1438         encoding = 0;
1439         word = NULL;
1440         start = inptr;
1441         while (inptr && *inptr) {
1442                 gunichar c;
1443                 const char *newinptr;
1444                 
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));
1450                         inptr++;
1451                         continue;
1452                 }
1453                 
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);
1459                                 start = word;
1460                         }
1461                         
1462                         switch (encoding) {
1463                         case 0:
1464                                 g_string_append_len (out, start, inptr - start);
1465                                 last_was_encoded = FALSE;
1466                                 break;
1467                         case 1:
1468                                 if (last_was_encoded)
1469                                         g_string_append_c (out, ' ');
1470                                 
1471                                 rfc2047_encode_word (out, start, inptr - start, "ISO-8859-1", CAMEL_MIME_IS_ESAFE);
1472                                 last_was_encoded = TRUE;
1473                                 break;
1474                         case 2:
1475                                 if (last_was_encoded)
1476                                         g_string_append_c (out, ' ');
1477                                 
1478                                 if (!(charset = camel_charset_best (start, inptr - start)))
1479                                         charset = "UTF-8";
1480                                 rfc2047_encode_word (out, start, inptr - start, charset, CAMEL_MIME_IS_ESAFE);
1481                                 last_was_encoded = TRUE;
1482                                 break;
1483                         }
1484                         
1485                         last_was_space = TRUE;
1486                         start = inptr;
1487                         word = NULL;
1488                         encoding = 0;
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;
1497                 }
1498                 
1499                 if (!(c < 256 && camel_mime_is_lwsp (c)) && !word)
1500                         word = inptr;
1501                 
1502                 inptr = newinptr;
1503         }
1504         
1505         if (inptr - start) {
1506                 if (word && !(last_was_encoded && encoding)) {
1507                         g_string_append_len (out, start, word - start);
1508                         start = word;
1509                 }
1510                 
1511                 switch (encoding) {
1512                 case 0:
1513                         g_string_append_len (out, start, inptr - start);
1514                         break;
1515                 case 1:
1516                         if (last_was_encoded)
1517                                 g_string_append_c (out, ' ');
1518                         
1519                         rfc2047_encode_word (out, start, inptr - start, "ISO-8859-1", CAMEL_MIME_IS_ESAFE);
1520                         break;
1521                 case 2:
1522                         if (last_was_encoded)
1523                                 g_string_append_c (out, ' ');
1524                         
1525                         if (!(charset = camel_charset_best (start, inptr - start)))
1526                                 charset = "UTF-8";
1527                         rfc2047_encode_word (out, start, inptr - start, charset, CAMEL_MIME_IS_ESAFE);
1528                         break;
1529                 }
1530         }
1531         
1532         outstr = out->str;
1533         g_string_free (out, FALSE);
1534         
1535         return outstr;
1536 }
1537
1538 /* apply quoted-string rules to a string */
1539 static void
1540 quote_word(GString *out, gboolean do_quotes, const char *start, size_t len)
1541 {
1542         int i, c;
1543
1544         /* TODO: What about folding on long lines? */
1545         if (do_quotes)
1546                 g_string_append_c(out, '"');
1547         for (i=0;i<len;i++) {
1548                 c = *start++;
1549                 if (c == '\"' || c=='\\' || c=='\r')
1550                         g_string_append_c(out, '\\');
1551                 g_string_append_c(out, c);
1552         }
1553         if (do_quotes)
1554                 g_string_append_c(out, '"');
1555 }
1556
1557 /* incrementing possibility for the word type */
1558 enum _phrase_word_t {
1559         WORD_ATOM,
1560         WORD_QSTRING,
1561         WORD_2047
1562 };
1563
1564 struct _phrase_word {
1565         const unsigned char *start, *end;
1566         enum _phrase_word_t type;
1567         int encoding;
1568 };
1569
1570 static gboolean
1571 word_types_compatable (enum _phrase_word_t type1, enum _phrase_word_t type2)
1572 {
1573         switch (type1) {
1574         case WORD_ATOM:
1575                 return type2 == WORD_QSTRING;
1576         case WORD_QSTRING:
1577                 return type2 != WORD_2047;
1578         case WORD_2047:
1579                 return type2 == WORD_2047;
1580         default:
1581                 return FALSE;
1582         }
1583 }
1584
1585 /* split the input into words with info about each word
1586  * merge common word types clean up */
1587 static GList *
1588 header_encode_phrase_get_words (const unsigned char *in)
1589 {
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;
1595         
1596         /* break the input into words */
1597         type = WORD_ATOM;
1598         last = inptr;
1599         start = inptr;
1600         encoding = 0;
1601         while (inptr && *inptr) {
1602                 gunichar c;
1603                 const char *newinptr;
1604                 
1605                 newinptr = g_utf8_next_char (inptr);
1606                 c = g_utf8_get_char (inptr);
1607                 
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));
1611                         inptr++;
1612                         continue;
1613                 }
1614                 
1615                 inptr = newinptr;
1616                 if (g_unichar_isspace (c)) {
1617                         if (count > 0) {
1618                                 word = g_new0 (struct _phrase_word, 1);
1619                                 word->start = start;
1620                                 word->end = last;
1621                                 word->type = type;
1622                                 word->encoding = encoding;
1623                                 words = g_list_append (words, word);
1624                                 count = 0;
1625                         }
1626                         
1627                         start = inptr;
1628                         type = WORD_ATOM;
1629                         encoding = 0;
1630                 } else {
1631                         count++;
1632                         if (c < 128) {
1633                                 if (!camel_mime_is_atom (c))
1634                                         type = MAX (type, WORD_QSTRING);
1635                         } else if (c > 127 && c < 256) {
1636                                 type = WORD_2047;
1637                                 encoding = MAX (encoding, 1);
1638                         } else if (c >= 256) {
1639                                 type = WORD_2047;
1640                                 encoding = MAX (encoding, 2);
1641                         }
1642                 }
1643                 
1644                 last = inptr;
1645         }
1646         
1647         if (count > 0) {
1648                 word = g_new0 (struct _phrase_word, 1);
1649                 word->start = start;
1650                 word->end = last;
1651                 word->type = type;
1652                 word->encoding = encoding;
1653                 words = g_list_append (words, word);
1654         }
1655         
1656         return words;
1657 }
1658
1659 #define MERGED_WORD_LT_FOLDLEN(wordlen, type) ((type) == WORD_2047 ? (wordlen) < CAMEL_FOLD_PREENCODED : (wordlen) < (CAMEL_FOLD_SIZE - 8))
1660
1661 static gboolean
1662 header_encode_phrase_merge_words (GList **wordsp)
1663 {
1664         GList *wordl, *nextl, *words = *wordsp;
1665         struct _phrase_word *word, *next;
1666         gboolean merged = FALSE;
1667         
1668         /* scan the list, checking for words of similar types that can be merged */
1669         wordl = words;
1670         while (wordl) {
1671                 word = wordl->data;
1672                 nextl = g_list_next (wordl);
1673                 
1674                 while (nextl) {
1675                         next = nextl->data;
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);
1685                                         g_free (next);
1686                                         
1687                                         nextl = g_list_next (wordl);
1688                                         
1689                                         merged = TRUE;
1690                                 } else {
1691                                         /* if it is going to be too long, make sure we include the
1692                                            separating whitespace */
1693                                         word->end = next->start;
1694                                         break;
1695                                 }
1696                         } else {
1697                                 break;
1698                         }
1699                 }
1700                 
1701                 wordl = g_list_next (wordl);
1702         }
1703         
1704         *wordsp = words;
1705         
1706         return merged;
1707 }
1708
1709 /* encodes a phrase sequence (different quoting/encoding rules to strings) */
1710 /**
1711  * camel_header_encode_phrase:
1712  * @in: header to encode
1713  *
1714  * Encodes a 'phrase' header according to the rules in rfc2047.
1715  *
1716  * Returns the encoded 'phrase'
1717  **/
1718 char *
1719 camel_header_encode_phrase (const unsigned char *in)
1720 {
1721         struct _phrase_word *word = NULL, *last_word = NULL;
1722         GList *words, *wordl;
1723         const char *charset;
1724         GString *out;
1725         char *outstr;
1726         
1727         if (in == NULL)
1728                 return NULL;
1729         
1730         words = header_encode_phrase_get_words (in);
1731         if (!words)
1732                 return NULL;
1733         
1734         while (header_encode_phrase_merge_words (&words))
1735                 ;
1736         
1737         out = g_string_new ("");
1738         
1739         /* output words now with spaces between them */
1740         wordl = words;
1741         while (wordl) {
1742                 const char *start;
1743                 size_t len;
1744                 
1745                 word = wordl->data;
1746                 
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);
1752                 }
1753                 
1754                 switch (word->type) {
1755                 case WORD_ATOM:
1756                         out = g_string_append_len (out, word->start, word->end - word->start);
1757                         break;
1758                 case WORD_QSTRING:
1759                         quote_word (out, TRUE, word->start, word->end - word->start);
1760                         break;
1761                 case WORD_2047:
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;
1767                                 
1768                                 /* encoded words need to be separated by linear whitespace */
1769                                 g_string_append_c (out, ' ');
1770                         } else {
1771                                 len = word->end - word->start;
1772                                 start = word->start;
1773                         }
1774                         
1775                         if (word->encoding == 1) {
1776                                 rfc2047_encode_word (out, start, len, "ISO-8859-1", CAMEL_MIME_IS_PSAFE);
1777                         } else {
1778                                 if (!(charset = camel_charset_best (start, len)))
1779                                         charset = "UTF-8";
1780                                 rfc2047_encode_word (out, start, len, charset, CAMEL_MIME_IS_PSAFE);
1781                         }
1782                         break;
1783                 }
1784                 
1785                 g_free (last_word);
1786                 wordl = g_list_next (wordl);
1787                 
1788                 last_word = word;
1789         }
1790         
1791         /* and we no longer need the list */
1792         g_free (word);
1793         g_list_free (words);
1794         
1795         outstr = out->str;
1796         g_string_free (out, FALSE);
1797         
1798         return outstr;
1799 }
1800
1801
1802 /* these are all internal parser functions */
1803
1804 static char *
1805 decode_token (const char **in)
1806 {
1807         const char *inptr = *in;
1808         const char *start;
1809         
1810         header_decode_lwsp (&inptr);
1811         start = inptr;
1812         while (camel_mime_is_ttoken (*inptr))
1813                 inptr++;
1814         if (inptr > start) {
1815                 *in = inptr;
1816                 return g_strndup (start, inptr - start);
1817         } else {
1818                 return NULL;
1819         }
1820 }
1821
1822
1823 /**
1824  * camel_header_token_decode:
1825  * @in: input string
1826  *
1827  * Gets the first token in the string according to the rules of
1828  * rfc0822.
1829  *
1830  * Returns a new string containing the first token in @in
1831  **/
1832 char *
1833 camel_header_token_decode(const char *in)
1834 {
1835         if (in == NULL)
1836                 return NULL;
1837
1838         return decode_token(&in);
1839 }
1840
1841 /*
1842    <"> * ( <any char except <"> \, cr  /  \ <any char> ) <">
1843 */
1844 static char *
1845 header_decode_quoted_string(const char **in)
1846 {
1847         const char *inptr = *in;
1848         char *out = NULL, *outptr;
1849         size_t outlen;
1850         int c;
1851
1852         header_decode_lwsp(&inptr);
1853         if (*inptr == '"') {
1854                 const char *intmp;
1855                 int skip = 0;
1856
1857                 /* first, calc length */
1858                 inptr++;
1859                 intmp = inptr;
1860                 while ( (c = *intmp++) && c!= '"') {
1861                         if (c=='\\' && *intmp) {
1862                                 intmp++;
1863                                 skip++;
1864                         }
1865                 }
1866                 outlen = intmp-inptr-skip;
1867                 out = outptr = g_malloc(outlen+1);
1868                 while ( (c = *inptr) && c!= '"') {
1869                         inptr++;
1870                         if (c=='\\' && *inptr) {
1871                                 c = *inptr++;
1872                         }
1873                         *outptr++ = c;
1874                 }
1875                 if (c)
1876                         inptr++;
1877                 *outptr = '\0';
1878         }
1879         *in = inptr;
1880         return out;
1881 }
1882
1883 static char *
1884 header_decode_atom(const char **in)
1885 {
1886         const char *inptr = *in, *start;
1887
1888         header_decode_lwsp(&inptr);
1889         start = inptr;
1890         while (camel_mime_is_atom(*inptr))
1891                 inptr++;
1892         *in = inptr;
1893         if (inptr > start)
1894                 return g_strndup(start, inptr-start);
1895         else
1896                 return NULL;
1897 }
1898
1899 static char *
1900 header_decode_word (const char **in)
1901 {
1902         const char *inptr = *in;
1903         
1904         header_decode_lwsp (&inptr);
1905         if (*inptr == '"') {
1906                 *in = inptr;
1907                 return header_decode_quoted_string (in);
1908         } else {
1909                 *in = inptr;
1910                 return header_decode_atom (in);
1911         }
1912 }
1913
1914 static char *
1915 header_decode_value(const char **in)
1916 {
1917         const char *inptr = *in;
1918
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);
1927         }
1928         return NULL;
1929 }
1930
1931 /* should this return -1 for no int? */
1932
1933 /**
1934  * camel_header_decode_int:
1935  * @in: pointer to input string
1936  *
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).
1939  *
1940  * Returns the int value
1941  **/
1942 int
1943 camel_header_decode_int(const char **in)
1944 {
1945         const char *inptr = *in;
1946         int c, v=0;
1947
1948         header_decode_lwsp(&inptr);
1949         while ( (c=*inptr++ & 0xff)
1950                 && isdigit(c) ) {
1951                 v = v*10+(c-'0');
1952         }
1953         *in = inptr-1;
1954         return v;
1955 }
1956
1957 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : tolower (c) - 'a' + 10)
1958
1959 static char *
1960 hex_decode (const char *in, size_t len)
1961 {
1962         const unsigned char *inend = in + len;
1963         unsigned char *inptr, *outptr;
1964         char *outbuf;
1965         
1966         outptr = outbuf = g_malloc (len + 1);
1967         
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]);
1973                                 inptr += 3;
1974                         } else
1975                                 *outptr++ = *inptr++;
1976                 } else
1977                         *outptr++ = *inptr++;
1978         }
1979         
1980         *outptr = '\0';
1981         
1982         return outbuf;
1983 }
1984
1985 /* Tries to convert @in @from charset @to charset.  Any failure, we get no data out rather than partial conversion */
1986 static char *
1987 header_convert(const char *to, const char *from, const char *in, size_t inlen)
1988 {
1989         iconv_t ic;
1990         size_t outlen, ret;
1991         char *outbuf, *outbase, *result = NULL;
1992
1993         ic = e_iconv_open(to, from);
1994         if (ic == (iconv_t) -1)
1995                 return NULL;
1996
1997         outlen = inlen * 6 + 16;
1998         outbuf = outbase = g_malloc(outlen);
1999                         
2000         ret = e_iconv(ic, &in, &inlen, &outbuf, &outlen);
2001         if (ret != (size_t) -1) {
2002                 e_iconv(ic, NULL, 0, &outbuf, &outlen);
2003                 *outbuf = '\0';
2004                 result = g_strdup(outbase);
2005         }
2006         e_iconv_close(ic);
2007         g_free(outbase);
2008
2009         return result;
2010 }
2011
2012 /* an rfc2184 encoded string looks something like:
2013  * us-ascii'en'This%20is%20even%20more%20
2014  */
2015
2016 static char *
2017 rfc2184_decode (const char *in, size_t len)
2018 {
2019         const char *inptr = in;
2020         const char *inend = in + len;
2021         const char *charset;
2022         char *decoded, *decword, *encoding;
2023         
2024         inptr = memchr (inptr, '\'', len);
2025         if (!inptr)
2026                 return NULL;
2027
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);
2032         
2033         inptr = memchr (inptr + 1, '\'', inend - inptr - 1);
2034         if (!inptr)
2035                 return NULL;
2036         inptr++;
2037         if (inptr >= inend)
2038                 return NULL;
2039
2040         decword = hex_decode (inptr, inend - inptr);
2041         decoded = header_convert("UTF-8", charset, decword, strlen(decword));
2042         g_free(decword);
2043
2044         return decoded;
2045 }
2046
2047
2048 /**
2049  * camel_header_param:
2050  * @params: parameters
2051  * @name: name of param to find
2052  *
2053  * Searches @params for a param named @name and gets the value.
2054  *
2055  * Returns the value of the @name param
2056  **/
2057 char *
2058 camel_header_param (struct _camel_header_param *p, const char *name)
2059 {
2060         while (p && g_ascii_strcasecmp (p->name, name) != 0)
2061                 p = p->next;
2062         if (p)
2063                 return p->value;
2064         return NULL;
2065 }
2066
2067
2068 /**
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
2073  *
2074  * Set a parameter in the list.
2075  *
2076  * Returns the set param
2077  **/
2078 struct _camel_header_param *
2079 camel_header_set_param (struct _camel_header_param **l, const char *name, const char *value)
2080 {
2081         struct _camel_header_param *p = (struct _camel_header_param *)l, *pn;
2082         
2083         if (name == NULL)
2084                 return NULL;
2085         
2086         while (p->next) {
2087                 pn = p->next;
2088                 if (!g_ascii_strcasecmp (pn->name, name)) {
2089                         g_free (pn->value);
2090                         if (value) {
2091                                 pn->value = g_strdup (value);
2092                                 return pn;
2093                         } else {
2094                                 p->next = pn->next;
2095                                 g_free (pn->name);
2096                                 g_free (pn);
2097                                 return NULL;
2098                         }
2099                 }
2100                 p = pn;
2101         }
2102
2103         if (value == NULL)
2104                 return NULL;
2105
2106         pn = g_malloc (sizeof (*pn));
2107         pn->next = 0;
2108         pn->name = g_strdup (name);
2109         pn->value = g_strdup (value);
2110         p->next = pn;
2111
2112         return pn;
2113 }
2114
2115
2116 /**
2117  * camel_content_type_param:
2118  * @content_type: a #CamelContentType
2119  * @name: name of param to find
2120  *
2121  * Searches the params on s #CamelContentType for a param named @name
2122  * and gets the value.
2123  *
2124  * Returns the value of the @name param
2125  **/
2126 const char *
2127 camel_content_type_param (CamelContentType *t, const char *name)
2128 {
2129         if (t==NULL)
2130                 return NULL;
2131         return camel_header_param (t->params, name);
2132 }
2133
2134
2135 /**
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
2140  *
2141  * Set a parameter on @content_type.
2142  **/
2143 void
2144 camel_content_type_set_param (CamelContentType *t, const char *name, const char *value)
2145 {
2146         camel_header_set_param (&t->params, name, value);
2147 }
2148
2149
2150 /**
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.
2155  *
2156  * The subtype of "*" will match any subtype.  If @ct is %NULL, then
2157  * it will match the type "text/plain".
2158  *
2159  * Returns %TRUE if the content type @ct is of type @type/@subtype or
2160  * %FALSE otherwise
2161  **/
2162 int
2163 camel_content_type_is(CamelContentType *ct, const char *type, const char *subtype)
2164 {
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, "*")));
2170         }
2171
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))));
2177 }
2178
2179
2180 /**
2181  * camel_header_param_list_free:
2182  * @params: a list of params
2183  *
2184  * Free the list of params.
2185  **/
2186 void
2187 camel_header_param_list_free(struct _camel_header_param *p)
2188 {
2189         struct _camel_header_param *n;
2190
2191         while (p) {
2192                 n = p->next;
2193                 g_free(p->name);
2194                 g_free(p->value);
2195                 g_free(p);
2196                 p = n;
2197         }
2198 }
2199
2200
2201 /**
2202  * camel_content_type_new:
2203  * @type: the major type of the new content-type
2204  * @subtype: the subtype
2205  *
2206  * Create a new #CamelContentType.
2207  *
2208  * Returns the new #CamelContentType
2209  **/
2210 CamelContentType *
2211 camel_content_type_new(const char *type, const char *subtype)
2212 {
2213         CamelContentType *t = g_malloc(sizeof(*t));
2214
2215         t->type = g_strdup(type);
2216         t->subtype = g_strdup(subtype);
2217         t->params = NULL;
2218         t->refcount = 1;
2219         return t;
2220 }
2221
2222
2223 /**
2224  * camel_content_type_ref:
2225  * @content_type: a #CamelContentType
2226  *
2227  * Refs the content type.
2228  **/
2229 void
2230 camel_content_type_ref(CamelContentType *ct)
2231 {
2232         if (ct)
2233                 ct->refcount++;
2234 }
2235
2236
2237 /**
2238  * camel_content_type_unref:
2239  * @content_type: a #CamelContentType
2240  *
2241  * Unrefs, and potentially frees, the content type.
2242  **/
2243 void
2244 camel_content_type_unref(CamelContentType *ct)
2245 {
2246         if (ct) {
2247                 if (ct->refcount <= 1) {
2248                         camel_header_param_list_free(ct->params);
2249                         g_free(ct->type);
2250                         g_free(ct->subtype);
2251                         g_free(ct);
2252                         ct = NULL;
2253                 } else {
2254                         ct->refcount--;
2255                 }
2256         }
2257 }
2258
2259 /* for decoding email addresses, canonically */
2260 static char *
2261 header_decode_domain(const char **in)
2262 {
2263         const char *inptr = *in;
2264         int go = TRUE;
2265         char *ret;
2266         GString *domain = g_string_new("");
2267
2268         /* domain ref | domain literal */
2269         header_decode_lwsp(&inptr);
2270         while (go) {
2271                 if (*inptr == '[') { /* domain literal */
2272                         domain = g_string_append_c(domain, '[');
2273                         inptr++;
2274                         header_decode_lwsp(&inptr);
2275                         while (camel_mime_is_dtext(*inptr) && *inptr) {
2276                                 domain = g_string_append_c(domain, *inptr);
2277                                 inptr++;
2278                         }
2279                         if (*inptr == ']') {
2280                                 domain = g_string_append_c(domain, ']');
2281                                 inptr++;
2282                         } else {
2283                                 w(g_warning("closing ']' not found in domain: %s", *in));
2284                         }
2285                 } else {
2286                         char *a = header_decode_atom(&inptr);
2287                         if (a) {
2288                                 domain = g_string_append(domain, a);
2289                                 g_free(a);
2290                         } else {
2291                                 w(g_warning("missing atom from domain-ref"));
2292                                 break;
2293                         }
2294                 }
2295                 header_decode_lwsp(&inptr);
2296                 if (*inptr == '.') { /* next sub-domain? */
2297                         domain = g_string_append_c(domain, '.');
2298                         inptr++;
2299                         header_decode_lwsp(&inptr);
2300                 } else
2301                         go = FALSE;
2302         }
2303
2304         *in = inptr;
2305
2306         ret = domain->str;
2307         g_string_free(domain, FALSE);
2308         return ret;
2309 }
2310
2311 static char *
2312 header_decode_addrspec(const char **in)
2313 {
2314         const char *inptr = *in;
2315         char *word;
2316         GString *addr = g_string_new("");
2317
2318         header_decode_lwsp(&inptr);
2319
2320         /* addr-spec */
2321         word = header_decode_word (&inptr);
2322         if (word) {
2323                 addr = g_string_append(addr, word);
2324                 header_decode_lwsp(&inptr);
2325                 g_free(word);
2326                 while (*inptr == '.' && word) {
2327                         inptr++;
2328                         addr = g_string_append_c(addr, '.');
2329                         word = header_decode_word (&inptr);
2330                         if (word) {
2331                                 addr = g_string_append(addr, word);
2332                                 header_decode_lwsp(&inptr);
2333                                 g_free(word);
2334                         } else {
2335                                 w(g_warning("Invalid address spec: %s", *in));
2336                         }
2337                 }
2338                 if (*inptr == '@') {
2339                         inptr++;
2340                         addr = g_string_append_c(addr, '@');
2341                         word = header_decode_domain(&inptr);
2342                         if (word) {
2343                                 addr = g_string_append(addr, word);
2344                                 g_free(word);
2345                         } else {
2346                                 w(g_warning("Invalid address, missing domain: %s", *in));
2347                         }
2348                 } else {
2349                         w(g_warning("Invalid addr-spec, missing @: %s", *in));
2350                 }
2351         } else {
2352                 w(g_warning("invalid addr-spec, no local part"));
2353                 g_string_free(addr, TRUE);
2354
2355                 return NULL;
2356         }
2357
2358         /* FIXME: return null on error? */
2359
2360         *in = inptr;
2361         word = addr->str;
2362         g_string_free(addr, FALSE);
2363         return word;
2364 }
2365
2366 /*
2367   address:
2368    word *('.' word) @ domain |
2369    *(word) '<' [ *('@' domain ) ':' ] word *( '.' word) @ domain |
2370
2371    1*word ':' [ word ... etc (mailbox, as above) ] ';'
2372  */
2373
2374 /* mailbox:
2375    word *( '.' word ) '@' domain
2376    *(word) '<' [ *('@' domain ) ':' ] word *( '.' word) @ domain
2377    */
2378
2379 static struct _camel_header_address *
2380 header_decode_mailbox(const char **in, const char *charset)
2381 {
2382         const char *inptr = *in;
2383         char *pre;
2384         int closeme = FALSE;
2385         GString *addr;
2386         GString *name = NULL;
2387         struct _camel_header_address *address = NULL;
2388         const char *comment = NULL;
2389
2390         addr = g_string_new("");
2391
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 ("");
2398                 while (pre) {
2399                         char *text, *last;
2400
2401                         /* perform internationalised decoding, and append */
2402                         text = camel_header_decode_string (pre, charset);
2403                         g_string_append (name, text);
2404                         last = pre;
2405                         g_free(text);
2406
2407                         pre = header_decode_word (&inptr);
2408                         if (pre) {
2409                                 size_t l = strlen (last);
2410                                 size_t p = strlen (pre);
2411                                 
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 ' ' */
2416                                 } else {
2417                                         name = g_string_append_c(name, ' ');
2418                                 }
2419                         } else {
2420                                 /* Fix for stupidly-broken-mailers that like to put '.''s in names unquoted */
2421                                 /* see bug #8147 */
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);
2426                                 }
2427                         }
2428                         g_free(last);
2429                 }
2430                 header_decode_lwsp(&inptr);
2431                 if (*inptr == '<') {
2432                         closeme = TRUE;
2433                 try_address_again:
2434                         inptr++;
2435                         header_decode_lwsp(&inptr);
2436                         if (*inptr == '@') {
2437                                 while (*inptr == '@') {
2438                                         inptr++;
2439                                         header_decode_domain(&inptr);
2440                                         header_decode_lwsp(&inptr);
2441                                         if (*inptr == ',') {
2442                                                 inptr++;
2443                                                 header_decode_lwsp(&inptr);
2444                                         }
2445                                 }
2446                                 if (*inptr == ':') {
2447                                         inptr++;
2448                                 } else {
2449                                         w(g_warning("broken route-address, missing ':': %s", *in));
2450                                 }
2451                         }
2452                         pre = header_decode_word (&inptr);
2453                         /*header_decode_lwsp(&inptr);*/
2454                 } else {
2455                         w(g_warning("broken address? %s", *in));
2456                 }
2457         }
2458
2459         if (pre) {
2460                 addr = g_string_append(addr, pre);
2461         } else {
2462                 w(g_warning("No local-part for email address: %s", *in));
2463         }
2464
2465         /* should be at word '.' localpart */
2466         while (*inptr == '.' && pre) {
2467                 inptr++;
2468                 g_free(pre);
2469                 pre = header_decode_word (&inptr);
2470                 addr = g_string_append_c(addr, '.');
2471                 if (pre)
2472                         addr = g_string_append(addr, pre);
2473                 comment = inptr;
2474                 header_decode_lwsp(&inptr);
2475         }
2476         g_free(pre);
2477
2478         /* now at '@' domain part */
2479         if (*inptr == '@') {
2480                 char *dom;
2481
2482                 inptr++;
2483                 addr = g_string_append_c(addr, '@');
2484                 comment = inptr;
2485                 dom = header_decode_domain(&inptr);
2486                 addr = g_string_append(addr, dom);
2487                 g_free(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 != ',') {
2492                         char *text;
2493
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));
2496
2497                         /* need to keep *inptr, as try_address_again will drop the current character */
2498                         if (*inptr == '<')
2499                                 closeme = TRUE;
2500                         else
2501                                 g_string_append_c(addr, *inptr);
2502
2503                         /* check for address is encoded word ... */
2504                         text = camel_header_decode_string(addr->str, charset);
2505                         if (name == NULL) {
2506                                 name = addr;
2507                                 addr = g_string_new("");
2508                                 if (text) {
2509                                         g_string_truncate(name, 0);
2510                                         g_string_append(name, text);
2511                                 }
2512                         }/* else {
2513                                 g_string_append(name, text?text:addr->str);
2514                                 g_string_truncate(addr, 0);
2515                         }*/
2516                         g_free(text);
2517
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);
2520                         if (text) {
2521                                 g_string_truncate(name, 0);
2522                                 g_string_append(name, text);
2523                                 g_free(text);
2524                         }
2525
2526                         goto try_address_again;
2527                 }
2528                 w(g_warning("invalid address, no '@' domain part at %c: %s", *inptr, *in));
2529         }
2530
2531         if (closeme) {
2532                 header_decode_lwsp(&inptr);
2533                 if (*inptr == '>') {
2534                         inptr++;
2535                 } else {
2536                         w(g_warning("invalid route address, no closing '>': %s", *in));
2537                 } 
2538         } else if (name == NULL && comment != NULL && inptr>comment) { /* check for comment after address */
2539                 char *text, *tmp;
2540                 const char *comstart, *comend;
2541
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 ... */
2546
2547                 d(printf("checking for comment from '%s'\n", comment));
2548
2549                 comstart = strchr(comment, '(');
2550                 if (comstart) {
2551                         comstart++;
2552                         header_decode_lwsp(&inptr);
2553                         comend = inptr-1;
2554                         while (comend > comstart && comend[0] != ')')
2555                                 comend--;
2556                         
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);
2562                                 g_free (tmp);
2563                                 g_free (text);
2564                         }
2565                 }
2566         }
2567         
2568         *in = inptr;
2569         
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;
2574                         GString *out;
2575                         
2576                         locale_charset = e_iconv_locale_charset ();
2577                         
2578                         out = g_string_new ("");
2579                         
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);
2583                         
2584                         g_string_free (addr, TRUE);
2585                         addr = out;
2586                 }
2587                 
2588                 address = camel_header_address_new_name(name ? name->str : "", addr->str);
2589         }
2590         
2591         d(printf("got mailbox: %s\n", addr->str));
2592         
2593         g_string_free(addr, TRUE);
2594         if (name)
2595                 g_string_free(name, TRUE);
2596         
2597         return address;
2598 }
2599
2600 static struct _camel_header_address *
2601 header_decode_address(const char **in, const char *charset)
2602 {
2603         const char *inptr = *in;
2604         char *pre;
2605         GString *group = g_string_new("");
2606         struct _camel_header_address *addr = NULL, *member;
2607
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, " ");
2613                 g_free(pre);
2614         }
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 */
2620                 inptr++;
2621                 /* FIXME: check rfc 2047 encodings of words, here or above in the loop */
2622                 header_decode_lwsp(&inptr);
2623                 if (*inptr != ';') {
2624                         int go = TRUE;
2625                         do {
2626                                 member = header_decode_mailbox(&inptr, charset);
2627                                 if (member)
2628                                         camel_header_address_add_member(addr, member);
2629                                 header_decode_lwsp(&inptr);
2630                                 if (*inptr == ',')
2631                                         inptr++;
2632                                 else
2633                                         go = FALSE;
2634                         } while (go);
2635                         if (*inptr == ';') {
2636                                 inptr++;
2637                         } else {
2638                                 w(g_warning("Invalid group spec, missing closing ';': %s", *in));
2639                         }
2640                 } else {
2641                         inptr++;
2642                 }
2643                 *in = inptr;
2644         } else {
2645                 addr = header_decode_mailbox(in, charset);
2646         }
2647
2648         g_string_free(group, TRUE);
2649
2650         return addr;
2651 }
2652
2653 static char *
2654 header_msgid_decode_internal(const char **in)
2655 {
2656         const char *inptr = *in;
2657         char *msgid = NULL;
2658
2659         d(printf("decoding Message-ID: '%s'\n", *in));
2660
2661         header_decode_lwsp(&inptr);
2662         if (*inptr == '<') {
2663                 inptr++;
2664                 header_decode_lwsp(&inptr);
2665                 msgid = header_decode_addrspec(&inptr);
2666                 if (msgid) {
2667                         header_decode_lwsp(&inptr);
2668                         if (*inptr == '>') {
2669                                 inptr++;
2670                         } else {
2671                                 w(g_warning("Missing closing '>' on message id: %s", *in));
2672                         }
2673                 } else {
2674                         w(g_warning("Cannot find message id in: %s", *in));
2675                 }
2676         } else {
2677                 w(g_warning("missing opening '<' on message id: %s", *in));
2678         }
2679         *in = inptr;
2680
2681         return msgid;
2682 }
2683
2684
2685 /**
2686  * camel_header_msgid_decode:
2687  * @in: input string
2688  *
2689  * Extract a message-id token from @in.
2690  *
2691  * Returns the msg-id
2692  **/
2693 char *
2694 camel_header_msgid_decode(const char *in)
2695 {
2696         if (in == NULL)
2697                 return NULL;
2698
2699         return header_msgid_decode_internal(&in);
2700 }
2701
2702
2703 /**
2704  * camel_header_contentid_decode:
2705  * @in: input string
2706  *
2707  * Extract a content-id from @in.
2708  *
2709  * Returns the extracted content-id
2710  **/
2711 char *
2712 camel_header_contentid_decode (const char *in)
2713 {
2714         const char *inptr = in;
2715         gboolean at = FALSE;
2716         GString *addr;
2717         char *buf;
2718         
2719         d(printf("decoding Content-ID: '%s'\n", in));
2720         
2721         header_decode_lwsp (&inptr);
2722         
2723         /* some lame mailers quote the Content-Id */
2724         if (*inptr == '"')
2725                 inptr++;
2726         
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)
2730                 return buf;
2731         
2732         g_free (buf);
2733         
2734         /* ugh, not a valid msg-id - try to get something useful out of it then? */
2735         inptr = in;
2736         header_decode_lwsp (&inptr);
2737         if (*inptr == '<') {
2738                 inptr++;
2739                 header_decode_lwsp (&inptr);
2740         }
2741         
2742         /* Eudora has been known to use <.@> as a content-id */
2743         if (!(buf = header_decode_word (&inptr)) && !strchr (".@", *inptr))
2744                 return NULL;
2745         
2746         addr = g_string_new ("");
2747         header_decode_lwsp (&inptr);
2748         while (buf != NULL || *inptr == '.' || (*inptr == '@' && !at)) {
2749                 if (buf != NULL) {
2750                         g_string_append (addr, buf);
2751                         g_free (buf);
2752                         buf = NULL;
2753                 }
2754                 
2755                 if (!at) {
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);
2762                                 at = TRUE;
2763                         }
2764                 } else if (strchr (".[]", *inptr)) {
2765                         g_string_append_c (addr, *inptr++);
2766                         buf = header_decode_atom (&inptr);
2767                 }
2768                 
2769                 header_decode_lwsp (&inptr);
2770         }
2771         
2772         buf = addr->str;
2773         g_string_free (addr, FALSE);
2774         
2775         return buf;
2776 }
2777
2778 void
2779 camel_header_references_list_append_asis(struct _camel_header_references **list, char *ref)
2780 {
2781         struct _camel_header_references *w = (struct _camel_header_references *)list, *n;
2782         while (w->next)
2783                 w = w->next;
2784         n = g_malloc(sizeof(*n));
2785         n->id = ref;
2786         n->next = 0;
2787         w->next = n;
2788 }
2789
2790 int
2791 camel_header_references_list_size(struct _camel_header_references **list)
2792 {
2793         int count = 0;
2794         struct _camel_header_references *w = *list;
2795         while (w) {
2796                 count++;
2797                 w = w->next;
2798         }
2799         return count;
2800 }
2801
2802 void
2803 camel_header_references_list_clear(struct _camel_header_references **list)
2804 {
2805         struct _camel_header_references *w = *list, *n;
2806         while (w) {
2807                 n = w->next;
2808                 g_free(w->id);
2809                 g_free(w);
2810                 w = n;
2811         }
2812         *list = NULL;
2813 }
2814
2815 static void
2816 header_references_decode_single (const char **in, struct _camel_header_references **head)
2817 {
2818         struct _camel_header_references *ref;
2819         const char *inptr = *in;
2820         char *id, *word;
2821         
2822         while (*inptr) {
2823                 header_decode_lwsp (&inptr);
2824                 if (*inptr == '<') {
2825                         id = header_msgid_decode_internal (&inptr);
2826                         if (id) {
2827                                 ref = g_malloc (sizeof (struct _camel_header_references));
2828                                 ref->next = *head;
2829                                 ref->id = id;
2830                                 *head = ref;
2831                                 break;
2832                         }
2833                 } else {
2834                         word = header_decode_word (&inptr);
2835                         if (word)
2836                                 g_free (word);
2837                         else if (*inptr != '\0')
2838                                 inptr++; /* Stupid mailer tricks */
2839                 }
2840         }
2841         
2842         *in = inptr;
2843 }
2844
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)
2848 {
2849         struct _camel_header_references *ref = NULL;
2850         
2851         if (in == NULL || in[0] == '\0')
2852                 return NULL;
2853         
2854         header_references_decode_single (&in, &ref);
2855         
2856         return ref;
2857 }
2858
2859 /* generate a list of references, from most recent up */
2860 struct _camel_header_references *
2861 camel_header_references_decode (const char *in)
2862 {
2863         struct _camel_header_references *refs = NULL;
2864         
2865         if (in == NULL || in[0] == '\0')
2866                 return NULL;
2867         
2868         while (*in)
2869                 header_references_decode_single (&in, &refs);
2870         
2871         return refs;
2872 }
2873
2874 struct _camel_header_references *
2875 camel_header_references_dup(const struct _camel_header_references *list)
2876 {
2877         struct _camel_header_references *new = NULL, *tmp;
2878
2879         while (list) {
2880                 tmp = g_new(struct _camel_header_references, 1);
2881                 tmp->next = new;
2882                 tmp->id = g_strdup(list->id);
2883                 new = tmp;
2884                 list = list->next;
2885         }
2886         return new;
2887 }
2888
2889 struct _camel_header_address *
2890 camel_header_mailbox_decode(const char *in, const char *charset)
2891 {
2892         if (in == NULL)
2893                 return NULL;
2894
2895         return header_decode_mailbox(&in, charset);
2896 }
2897
2898 struct _camel_header_address *
2899 camel_header_address_decode(const char *in, const char *charset)
2900 {
2901         const char *inptr = in, *last;
2902         struct _camel_header_address *list = NULL, *addr;
2903
2904         d(printf("decoding To: '%s'\n", in));
2905
2906         if (in == NULL)
2907                 return NULL;
2908
2909         header_decode_lwsp(&inptr);
2910         if (*inptr == 0)
2911                 return NULL;
2912
2913         do {
2914                 last = inptr;
2915                 addr = header_decode_address(&inptr, charset);
2916                 if (addr)
2917                         camel_header_address_list_append(&list, addr);
2918                 header_decode_lwsp(&inptr);
2919                 if (*inptr == ',')
2920                         inptr++;
2921                 else
2922                         break;
2923         } while (inptr != last);
2924
2925         if (*inptr) {
2926                 w(g_warning("Invalid input detected at %c (%d): %s\n or at: %s", *inptr, inptr-in, in, inptr));
2927         }
2928
2929         if (inptr == last) {
2930                 w(g_warning("detected invalid input loop at : %s", last));
2931         }
2932
2933         return list;
2934 }
2935
2936 struct _camel_header_newsgroup *
2937 camel_header_newsgroups_decode(const char *in)
2938 {
2939         const char *inptr = in;
2940         register char c;
2941         struct _camel_header_newsgroup *head, *last, *ng;
2942         const char *start;
2943
2944         head = NULL;
2945         last = (struct _camel_header_newsgroup *)&head;
2946
2947         do {
2948                 header_decode_lwsp(&inptr);
2949                 start = inptr;
2950                 while ((c = *inptr++) && !camel_mime_is_lwsp(c) && c != ',')
2951                         ;
2952                 if (start != inptr-1) {
2953                         ng = g_malloc(sizeof(*ng));
2954                         ng->newsgroup = g_strndup(start, inptr-start-1);
2955                         ng->next = NULL;
2956                         last->next = ng;
2957                         last = ng;
2958                 }
2959         } while (c);
2960
2961         return head;
2962 }
2963
2964 void
2965 camel_header_newsgroups_free(struct _camel_header_newsgroup *ng)
2966 {
2967         while (ng) {
2968                 struct _camel_header_newsgroup *nng = ng->next;
2969
2970                 g_free(ng->newsgroup);
2971                 g_free(ng);
2972                 ng = nng;
2973         }
2974 }
2975
2976 /* this must be kept in sync with the header */
2977 static const char *encodings[] = {
2978         "",
2979         "7bit",
2980         "8bit",
2981         "base64",
2982         "quoted-printable",
2983         "binary",
2984         "x-uuencode",
2985 };
2986
2987 const char *
2988 camel_transfer_encoding_to_string (CamelTransferEncoding encoding)
2989 {
2990         if (encoding >= sizeof (encodings) / sizeof (encodings[0]))
2991                 encoding = 0;
2992         
2993         return encodings[encoding];
2994 }
2995
2996 CamelTransferEncoding
2997 camel_transfer_encoding_from_string (const char *string)
2998 {
2999         int i;
3000         
3001         if (string != NULL) {
3002                 for (i = 0; i < sizeof (encodings) / sizeof (encodings[0]); i++)
3003                         if (!g_ascii_strcasecmp (string, encodings[i]))
3004                                 return i;
3005         }
3006         
3007         return CAMEL_TRANSFER_ENCODING_DEFAULT;
3008 }
3009
3010 void
3011 camel_header_mime_decode(const char *in, int *maj, int *min)
3012 {
3013         const char *inptr = in;
3014         int major=-1, minor=-1;
3015
3016         d(printf("decoding MIME-Version: '%s'\n", in));
3017
3018         if (in != NULL) {
3019                 header_decode_lwsp(&inptr);
3020                 if (isdigit(*inptr)) {
3021                         major = camel_header_decode_int(&inptr);
3022                         header_decode_lwsp(&inptr);
3023                         if (*inptr == '.') {
3024                                 inptr++;
3025                                 header_decode_lwsp(&inptr);
3026                                 if (isdigit(*inptr))
3027                                         minor = camel_header_decode_int(&inptr);
3028                         }
3029                 }
3030         }
3031
3032         if (maj)
3033                 *maj = major;
3034         if (min)
3035                 *min = minor;
3036
3037         d(printf("major = %d, minor = %d\n", major, minor));
3038 }
3039
3040 struct _rfc2184_param {
3041         struct _camel_header_param param;
3042         int index;
3043 };
3044
3045 static int
3046 rfc2184_param_cmp(const void *ap, const void *bp)
3047 {
3048         const struct _rfc2184_param *a = *(void **)ap;
3049         const struct _rfc2184_param *b = *(void **)bp;
3050         int res;
3051
3052         res = strcmp(a->param.name, b->param.name);
3053         if (res == 0) {
3054                 if (a->index > b->index)
3055                         res = 1;
3056                 else if (a->index < b->index)
3057                         res = -1;
3058         }
3059                 
3060         return res;
3061 }
3062
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)
3066 {
3067         struct _camel_header_param *node;
3068
3069         /* This handles -
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?
3072         */
3073         node = g_malloc(sizeof(*node));
3074         last->next = node;
3075         node->next = NULL;
3076         node->name = name;
3077         if (strncmp(value, "=?", 2) == 0
3078             && (node->value = header_decode_text(value, strlen(value), FALSE, NULL))) {
3079                 g_free(value);
3080         } else if (g_ascii_strcasecmp (name, "boundary") != 0 && !g_utf8_validate(value, -1, NULL)) {
3081                 const char *charset = e_iconv_locale_charset();
3082                 
3083                 if ((node->value = header_convert("UTF-8", charset?charset:"ISO-8859-1", value, strlen(value)))) {
3084                         g_free(value);
3085                 } else {
3086                         node->value = value;
3087                         for (;*value;value++)
3088                                 if (!isascii((unsigned char)*value))
3089                                         *value = '_';
3090                 }
3091         } else
3092                 node->value = value;
3093
3094         return node;
3095 }
3096
3097 static struct _camel_header_param *
3098 header_decode_param_list (const char **in)
3099 {
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;
3104         char *tmp;
3105
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 == ';') {
3109                 char *name;
3110                 char *value = NULL;
3111
3112                 inptr++;
3113                 name = decode_token(&inptr);
3114                 header_decode_lwsp(&inptr);
3115                 if (*inptr == '=') {
3116                         inptr++;
3117                         value = header_decode_value(&inptr);
3118                 }
3119
3120                 if (name && value) {
3121                         char *index = strchr(name, '*');
3122
3123                         if (index) {
3124                                 if (index[1] == 0) {
3125                                         /* VAL*="foo", decode immediately and append */
3126                                         *index = 0;
3127                                         tmp = rfc2184_decode(value, strlen(value));
3128                                         if (tmp) {
3129                                                 g_free(value);
3130                                                 value = tmp;
3131                                         }
3132                                         last = header_append_param(last, name, value);
3133                                 } else {
3134                                         /* VAL*1="foo", save for later */
3135                                         *index++ = 0;
3136                                         work = g_malloc(sizeof(*work));
3137                                         work->param.name = name;
3138                                         work->param.value = value;
3139                                         work->index = atoi(index);
3140                                         if (split == NULL)
3141                                                 split = g_ptr_array_new();
3142                                         g_ptr_array_add(split, work);
3143                                 }
3144                         } else {
3145                                 last = header_append_param(last, name, value);
3146                         }
3147                 } else {
3148                         g_free(name);
3149                         g_free(value);
3150                 }
3151
3152                 header_decode_lwsp(&inptr);
3153         }
3154
3155         /* Rejoin any RFC 2184 split parameters in the proper order */
3156         /* Parameters with the same index will be concatenated in undefined order */
3157         if (split) {
3158                 GString *value = g_string_new("");
3159                 struct _rfc2184_param *first;
3160                 int i;
3161
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);
3170                                 if (tmp == NULL)
3171                                         tmp = g_strdup(value->str);
3172
3173                                 last = header_append_param(last, g_strdup(first->param.name), tmp);
3174                                 g_string_truncate(value, 0);
3175                                 first = work;
3176                         }
3177                         if (split->len-1 != i)
3178                                 g_string_append(value, work->param.value);
3179                 }
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);
3185                         g_free(work);
3186                 }
3187                 g_ptr_array_free(split, TRUE);
3188         }
3189
3190         *in = inptr;
3191
3192         return head;
3193 }
3194
3195 struct _camel_header_param *
3196 camel_header_param_list_decode(const char *in)
3197 {
3198         if (in == NULL)
3199                 return NULL;
3200
3201         return header_decode_param_list(&in);
3202 }
3203
3204 static char *
3205 header_encode_param (const unsigned char *in, gboolean *encoded)
3206 {
3207         const unsigned char *inptr = in;
3208         unsigned char *outbuf = NULL;
3209         const char *charset;
3210         GString *out;
3211         guint32 c;
3212         char *str;
3213         
3214         *encoded = FALSE;
3215         
3216         g_return_val_if_fail (in != NULL, NULL);
3217
3218         /* if we have really broken utf8 passed in, we just treat it as binary data */
3219
3220         charset = camel_charset_best(in, strlen(in));
3221         if (charset == NULL)
3222                 return g_strdup(in);
3223         
3224         if (g_ascii_strcasecmp(charset, "UTF-8") != 0) {
3225                 if ((outbuf = header_convert(charset, "UTF-8", in, strlen(in))))
3226                         inptr = outbuf;
3227                 else
3228                         return g_strdup(in);
3229         }
3230         
3231         /* FIXME: set the 'language' as well, assuming we can get that info...? */
3232         out = g_string_new (charset);
3233         g_string_append(out, "''");
3234
3235         while ( (c = *inptr++) ) {
3236                 if (camel_mime_is_attrchar(c))
3237                         g_string_append_c (out, c);
3238                 else
3239                         g_string_append_printf (out, "%%%c%c", tohex[(c >> 4) & 0xf], tohex[c & 0xf]);
3240         }
3241         g_free (outbuf);
3242         
3243         str = out->str;
3244         g_string_free (out, FALSE);
3245         *encoded = TRUE;
3246         
3247         return str;
3248 }
3249
3250 void
3251 camel_header_param_list_format_append (GString *out, struct _camel_header_param *p)
3252 {
3253         int used = out->len;
3254         
3255         while (p) {
3256                 gboolean encoded = FALSE;
3257                 gboolean quote = FALSE;
3258                 int here = out->len;
3259                 size_t nlen, vlen;
3260                 char *value;
3261                 
3262                 if (!p->value) {
3263                         p = p->next;
3264                         continue;
3265                 }
3266                 
3267                 value = header_encode_param (p->value, &encoded);
3268                 if (!value) {
3269                         w(g_warning ("appending parameter %s=%s violates rfc2184", p->name, p->value));
3270                         value = g_strdup (p->value);
3271                 }
3272                 
3273                 if (!encoded) {
3274                         char *ch;
3275                         
3276                         for (ch = value; *ch; ch++) {
3277                                 if (camel_mime_is_tspecial (*ch) || camel_mime_is_lwsp (*ch))
3278                                         break;
3279                         }
3280                         
3281                         quote = ch && *ch;
3282                 }
3283                 
3284                 nlen = strlen (p->name);
3285                 vlen = strlen (value);
3286                 
3287                 if (used + nlen + vlen > CAMEL_FOLD_SIZE - 8) {
3288                         out = g_string_append (out, ";\n\t");
3289                         here = out->len;
3290                         used = 0;
3291                 } else
3292                         out = g_string_append (out, "; ");
3293                 
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;
3298                         int i = 0;
3299                         
3300                         inptr = value;
3301                         inend = value + vlen;
3302                         
3303                         while (inptr < inend) {
3304                                 char *ptr = inptr + MIN (inend - inptr, maxlen);
3305                                 
3306                                 if (encoded && ptr < inend) {
3307                                         /* be careful not to break an encoded char (ie %20) */
3308                                         char *q = ptr;
3309                                         int j = 2;
3310                                         
3311                                         for ( ; j > 0 && q > inptr && *q != '%'; j--, q--);
3312                                         if (*q == '%')
3313                                                 ptr = q;
3314                                 }
3315                                 
3316                                 if (i != 0) {
3317                                         g_string_append (out, ";\n\t");
3318                                         here = out->len;
3319                                         used = 0;
3320                                 }
3321                                 
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);
3325                                 else
3326                                         quote_word (out, TRUE, inptr, ptr - inptr);
3327                                 
3328                                 d(printf ("wrote: %s\n", out->str + here));
3329                                 
3330                                 used += (out->len - here);
3331                                 
3332                                 inptr = ptr;
3333                         }
3334                 } else {
3335                         g_string_append_printf (out, "%s%s=", p->name, encoded ? "*" : "");
3336                         
3337                         if (encoded || !quote)
3338                                 g_string_append (out, value);
3339                         else
3340                                 quote_word (out, TRUE, value, vlen);
3341                         
3342                         used += (out->len - here);
3343                 }
3344                 
3345                 g_free (value);
3346                 
3347                 p = p->next;
3348         }
3349 }
3350
3351 char *
3352 camel_header_param_list_format(struct _camel_header_param *p)
3353 {
3354         GString *out = g_string_new("");
3355         char *ret;
3356
3357         camel_header_param_list_format_append(out, p);
3358         ret = out->str;
3359         g_string_free(out, FALSE);
3360         return ret;
3361 }
3362
3363 CamelContentType *
3364 camel_content_type_decode(const char *in)
3365 {
3366         const char *inptr = in;
3367         char *type, *subtype = NULL;
3368         CamelContentType *t = NULL;
3369
3370         if (in==NULL)
3371                 return NULL;
3372
3373         type = decode_token(&inptr);
3374         header_decode_lwsp(&inptr);
3375         if (type) {
3376                 if  (*inptr == '/') {
3377                         inptr++;
3378                         subtype = decode_token(&inptr);
3379                 }
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");
3383                 }
3384                 if (subtype == NULL) {
3385                         w(g_warning("MIME type with no subtype: %s", in));
3386                 }
3387
3388                 t = camel_content_type_new(type, subtype);
3389                 t->params = header_decode_param_list(&inptr);
3390                 g_free(type);
3391                 g_free(subtype);
3392         } else {
3393                 g_free(type);
3394                 d(printf("cannot find MIME type in header (2) '%s'", in));
3395         }
3396         return t;
3397 }
3398
3399 void
3400 camel_content_type_dump(CamelContentType *ct)
3401 {
3402         struct _camel_header_param *p;
3403
3404         printf("Content-Type: ");
3405         if (ct==NULL) {
3406                 printf("<NULL>\n");
3407                 return;
3408         }
3409         printf("%s / %s", ct->type, ct->subtype);
3410         p = ct->params;
3411         if (p) {
3412                 while (p) {
3413                         printf(";\n\t%s=\"%s\"", p->name, p->value);
3414                         p = p->next;
3415                 }
3416         }
3417         printf("\n");
3418 }
3419
3420 char *
3421 camel_content_type_format (CamelContentType *ct)
3422 {
3423         GString *out;
3424         char *ret;
3425         
3426         if (ct == NULL)
3427                 return NULL;
3428         
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);
3437                 else
3438                         g_string_append_printf (out, "%s", ct->type);
3439         } else {
3440                 g_string_append_printf (out, "%s/%s", ct->type, ct->subtype);
3441         }
3442         camel_header_param_list_format_append (out, ct->params);
3443         
3444         ret = out->str;
3445         g_string_free (out, FALSE);
3446         
3447         return ret;
3448 }
3449
3450 char *
3451 camel_content_type_simple (CamelContentType *ct)
3452 {
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);
3460                 else
3461                         return g_strdup (ct->type);
3462         } else
3463                 return g_strdup_printf ("%s/%s", ct->type, ct->subtype);
3464 }
3465
3466 char *
3467 camel_content_transfer_encoding_decode (const char *in)
3468 {
3469         if (in)
3470                 return decode_token (&in);
3471         
3472         return NULL;
3473 }
3474
3475 CamelContentDisposition *
3476 camel_content_disposition_decode(const char *in)
3477 {
3478         CamelContentDisposition *d = NULL;
3479         const char *inptr = in;
3480
3481         if (in == NULL)
3482                 return NULL;
3483
3484         d = g_malloc(sizeof(*d));
3485         d->refcount = 1;
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);
3490         return d;
3491 }
3492
3493 void
3494 camel_content_disposition_ref(CamelContentDisposition *d)
3495 {
3496         if (d)
3497                 d->refcount++;
3498 }
3499
3500 void
3501 camel_content_disposition_unref(CamelContentDisposition *d)
3502 {
3503         if (d) {
3504                 if (d->refcount<=1) {
3505                         camel_header_param_list_free(d->params);
3506                         g_free(d->disposition);
3507                         g_free(d);
3508                 } else {
3509                         d->refcount--;
3510                 }
3511         }
3512 }
3513
3514 char *
3515 camel_content_disposition_format(CamelContentDisposition *d)
3516 {
3517         GString *out;
3518         char *ret;
3519
3520         if (d==NULL)
3521                 return NULL;
3522
3523         out = g_string_new("");
3524         if (d->disposition)
3525                 out = g_string_append(out, d->disposition);
3526         else
3527                 out = g_string_append(out, "attachment");
3528         camel_header_param_list_format_append(out, d->params);
3529
3530         ret = out->str;
3531         g_string_free(out, FALSE);
3532         return ret;
3533 }
3534
3535 /* hrm, is there a library for this shit? */
3536 static struct {
3537         char *name;
3538         int offset;
3539 } tz_offsets [] = {
3540         { "UT", 0 },
3541         { "GMT", 0 },
3542         { "EST", -500 },        /* these are all US timezones.  bloody yanks */
3543         { "EDT", -400 },
3544         { "CST", -600 },
3545         { "CDT", -500 },
3546         { "MST", -700 },
3547         { "MDT", -600 },
3548         { "PST", -800 },
3549         { "PDT", -700 },
3550         { "Z", 0 },
3551         { "A", -100 },
3552         { "M", -1200 },
3553         { "N", 100 },
3554         { "Y", 1200 },
3555 };
3556
3557 static const char tz_months [][4] = {
3558         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
3559         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
3560 };
3561
3562 static const char tz_days [][4] = {
3563         "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
3564 };
3565
3566 char *
3567 camel_header_format_date(time_t time, int offset)
3568 {
3569         struct tm tm;
3570
3571         d(printf("offset = %d\n", offset));
3572
3573         d(printf("converting date %s", ctime(&time)));
3574
3575         time += ((offset / 100) * (60*60)) + (offset % 100)*60;
3576
3577         d(printf("converting date %s", ctime(&time)));
3578         
3579         gmtime_r (&time, &tm);
3580         
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],
3584                                tm.tm_year + 1900,
3585                                tm.tm_hour, tm.tm_min, tm.tm_sec,
3586                                offset);
3587 }
3588
3589 /* convert a date to time_t representation */
3590 /* this is an awful mess oh well */
3591 time_t
3592 camel_header_decode_date(const char *in, int *saveoffset)
3593 {
3594         const char *inptr = in;
3595         char *monthname;
3596         gboolean foundmonth;
3597         int year, offset = 0;
3598         struct tm tm;
3599         int i;
3600         time_t t;
3601
3602         if (in == NULL) {
3603                 if (saveoffset)
3604                         *saveoffset = 0;
3605                 return 0;
3606         }
3607
3608         d(printf ("\ndecoding date '%s'\n", inptr));
3609
3610         memset (&tm, 0, sizeof(tm));
3611
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 */
3616                 if (day) {
3617                         d(printf ("got day: %s\n", day));
3618                         g_free (day);
3619                         header_decode_lwsp (&inptr);
3620                         if (*inptr == ',') {
3621                                 inptr++;
3622                         } else {
3623 #ifndef CLEAN_DATE
3624                                 return parse_broken_date (in, saveoffset);
3625 #else
3626                                 if (saveoffset)
3627                                         *saveoffset = 0;
3628                                 return 0;
3629 #endif /* ! CLEAN_DATE */
3630                         }
3631                 }
3632         }
3633         tm.tm_mday = camel_header_decode_int(&inptr);
3634 #ifndef CLEAN_DATE
3635         if (tm.tm_mday == 0) {
3636                 return parse_broken_date (in, saveoffset);
3637         }
3638 #endif /* ! CLEAN_DATE */
3639
3640         monthname = decode_token(&inptr);
3641         foundmonth = FALSE;
3642         if (monthname) {
3643                 for (i=0;i<sizeof(tz_months)/sizeof(tz_months[0]);i++) {
3644                         if (!g_ascii_strcasecmp(tz_months[i], monthname)) {
3645                                 tm.tm_mon = i;
3646                                 foundmonth = TRUE;
3647                                 break;
3648                         }
3649                 }
3650                 g_free(monthname);
3651         }
3652 #ifndef CLEAN_DATE
3653         if (!foundmonth) {
3654                 return parse_broken_date (in, saveoffset);
3655         }
3656 #endif /* ! CLEAN_DATE */
3657
3658         year = camel_header_decode_int(&inptr);
3659         if (year < 69) {
3660                 tm.tm_year = 100 + year;
3661         } else if (year < 100) {
3662                 tm.tm_year = year;
3663         } else if (year >= 100 && year < 1900) {
3664                 tm.tm_year = year;
3665         } else {
3666                 tm.tm_year = year - 1900;
3667         }
3668         /* get the time ... yurck */
3669         tm.tm_hour = camel_header_decode_int(&inptr);
3670         header_decode_lwsp(&inptr);
3671         if (*inptr == ':')
3672                 inptr++;
3673         tm.tm_min = camel_header_decode_int(&inptr);
3674         header_decode_lwsp(&inptr);
3675         if (*inptr == ':')
3676                 inptr++;
3677         tm.tm_sec = camel_header_decode_int(&inptr);
3678         header_decode_lwsp(&inptr);
3679         if (*inptr == '+'
3680             || *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)
3685                         offset = 0;
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)
3690                         offset = 0;
3691         } else {
3692                 char *tz = decode_token(&inptr);
3693
3694                 if (tz) {
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;
3698                                         break;
3699                                 }
3700                         }
3701                         g_free(tz);
3702                 }
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);
3708                 }
3709                 d(printf("named offset = %d\n", offset));
3710         }
3711
3712         t = e_mktime_utc(&tm);
3713
3714         /* t is now GMT of the time we want, but not offset by the timezone ... */
3715
3716         d(printf(" gmt normalized? = %s\n", ctime(&t)));
3717
3718         /* this should convert the time to the GMT equiv time */
3719         t -= ( (offset/100) * 60*60) + (offset % 100)*60;
3720
3721         d(printf(" gmt normalized for timezone? = %s\n", ctime(&t)));
3722
3723         d({
3724                 char *tmp;
3725                 tmp = camel_header_format_date(t, offset);
3726                 printf(" encoded again: %s\n", tmp);
3727                 g_free(tmp);
3728         });
3729
3730         if (saveoffset)
3731                 *saveoffset = offset;
3732
3733         return t;
3734 }
3735
3736 char *
3737 camel_header_location_decode(const char *in)
3738 {
3739         int quote = 0;
3740         GString *out = g_string_new("");
3741         char c, *res;
3742
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.
3748          *
3749          * But Netscape puts quotes around the URI when sending web
3750          * pages.
3751          *
3752          * Which is required as defined in rfc2017 [3.1].  Although
3753          * outlook doesn't do this.
3754          *
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.
3758          */
3759
3760         header_decode_lwsp(&in);
3761         if (*in == '"') {
3762                 in++;
3763                 quote = 1;
3764         }
3765
3766         while ( (c = *in++) ) {
3767                 if (quote && c=='"')
3768                         break;
3769                 if (!camel_mime_is_lwsp(c))
3770                         g_string_append_c(out, c);
3771         }
3772
3773         res = g_strdup(out->str);
3774         g_string_free(out, TRUE);
3775
3776         return res;
3777 }
3778
3779 /* extra rfc checks */
3780 #define CHECKS
3781
3782 #ifdef CHECKS
3783 static void
3784 check_header(struct _camel_header_raw *h)
3785 {
3786         unsigned char *p;
3787
3788         p = h->value;
3789         while (p && *p) {
3790                 if (!isascii(*p)) {
3791                         w(g_warning("Appending header violates rfc: %s: %s", h->name, h->value));
3792                         return;
3793                 }
3794                 p++;
3795         }
3796 }
3797 #endif
3798
3799 void
3800 camel_header_raw_append_parse(struct _camel_header_raw **list, const char *header, int offset)
3801 {
3802         register const char *in;
3803         size_t fieldlen;
3804         char *name;
3805
3806         in = header;
3807         while (camel_mime_is_fieldname(*in) || *in==':')
3808                 in++;
3809         fieldlen = in-header-1;
3810         while (camel_mime_is_lwsp(*in))
3811                 in++;
3812         if (fieldlen == 0 || header[fieldlen] != ':') {
3813                 printf("Invalid header line: '%s'\n", header);
3814                 return;
3815         }
3816         name = g_alloca (fieldlen + 1);
3817         memcpy(name, header, fieldlen);
3818         name[fieldlen] = 0;
3819
3820         camel_header_raw_append(list, name, in, offset);
3821 }
3822
3823 void
3824 camel_header_raw_append(struct _camel_header_raw **list, const char *name, const char *value, int offset)
3825 {
3826         struct _camel_header_raw *l, *n;
3827
3828         d(printf("Header: %s: %s\n", name, value));
3829
3830         n = g_malloc(sizeof(*n));
3831         n->next = NULL;
3832         n->name = g_strdup(name);
3833         n->value = g_strdup(value);
3834         n->offset = offset;
3835 #ifdef CHECKS
3836         check_header(n);
3837 #endif
3838         l = (struct _camel_header_raw *)list;
3839         while (l->next) {
3840                 l = l->next;
3841         }
3842         l->next = n;
3843
3844         /* debug */
3845 #if 0
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);
3855         }
3856 #endif
3857 }
3858
3859 static struct _camel_header_raw *
3860 header_raw_find_node(struct _camel_header_raw **list, const char *name)
3861 {
3862         struct _camel_header_raw *l;
3863
3864         l = *list;
3865         while (l) {
3866                 if (!g_ascii_strcasecmp(l->name, name))
3867                         break;
3868                 l = l->next;
3869         }
3870         return l;
3871 }
3872
3873 const char *
3874 camel_header_raw_find(struct _camel_header_raw **list, const char *name, int *offset)
3875 {
3876         struct _camel_header_raw *l;
3877
3878         l = header_raw_find_node(list, name);
3879         if (l) {
3880                 if (offset)
3881                         *offset = l->offset;
3882                 return l->value;
3883         } else
3884                 return NULL;
3885 }
3886
3887 const char *
3888 camel_header_raw_find_next(struct _camel_header_raw **list, const char *name, int *offset, const char *last)
3889 {
3890         struct _camel_header_raw *l;
3891
3892         if (last == NULL || name == NULL)
3893                 return NULL;
3894
3895         l = *list;
3896         while (l && l->value != last)
3897                 l = l->next;
3898         return camel_header_raw_find(&l, name, offset);
3899 }
3900
3901 static void
3902 header_raw_free(struct _camel_header_raw *l)
3903 {
3904         g_free(l->name);
3905         g_free(l->value);
3906         g_free(l);
3907 }
3908
3909 void
3910 camel_header_raw_remove(struct _camel_header_raw **list, const char *name)
3911 {
3912         struct _camel_header_raw *l, *p;
3913
3914         /* the next pointer is at the head of the structure, so this is safe */
3915         p = (struct _camel_header_raw *)list;
3916         l = *list;
3917         while (l) {
3918                 if (!g_ascii_strcasecmp(l->name, name)) {
3919                         p->next = l->next;
3920                         header_raw_free(l);
3921                         l = p->next;
3922                 } else {
3923                         p = l;
3924                         l = l->next;
3925                 }
3926         }
3927 }
3928
3929 void
3930 camel_header_raw_replace(struct _camel_header_raw **list, const char *name, const char *value, int offset)
3931 {
3932         camel_header_raw_remove(list, name);
3933         camel_header_raw_append(list, name, value, offset);
3934 }
3935
3936 void
3937 camel_header_raw_clear(struct _camel_header_raw **list)
3938 {
3939         struct _camel_header_raw *l, *n;
3940         l = *list;
3941         while (l) {
3942                 n = l->next;
3943                 header_raw_free(l);
3944                 l = n;
3945         }
3946         *list = NULL;
3947 }
3948
3949 char *
3950 camel_header_msgid_generate (void)
3951 {
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];
3956         char *name;
3957         static int count = 0;
3958         char *msgid;
3959         int retval;
3960         struct addrinfo *ai = NULL, hints = { 0 };
3961
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;
3968                 else
3969                         name = host;
3970         } else
3971                 name = "localhost.localdomain";
3972         
3973         COUNT_LOCK ();
3974         msgid = g_strdup_printf ("%d.%d.%d.camel@%s", (int) time (NULL), getpid (), count++, name);
3975         COUNT_UNLOCK ();
3976         
3977         if (ai)
3978                 camel_freeaddrinfo(ai);
3979         
3980         return msgid;
3981 }
3982
3983
3984 static struct {
3985         char *name;
3986         char *pattern;
3987         regex_t regex;
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>]*)" },
4030 };
4031
4032 static pthread_once_t mailing_list_init_once = PTHREAD_ONCE_INIT;
4033
4034 static void
4035 mailing_list_init(void)
4036 {
4037         int i, errcode, failed=0;
4038
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);
4042                 if (errcode != 0) {
4043                         char *errstr;
4044                         size_t len;
4045                 
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);
4049                 
4050                         g_warning("Internal error, compiling regex failed: %s: %s", mail_list_magic[i].pattern, errstr);
4051                         g_free(errstr);
4052                         failed++;
4053                 }
4054         }
4055
4056         g_assert(failed == 0);
4057 }
4058
4059 char *
4060 camel_header_raw_check_mailing_list(struct _camel_header_raw **list)
4061 {
4062         const char *v;
4063         regmatch_t match[3];
4064         int i, j;
4065
4066         pthread_once(&mailing_list_init_once, mailing_list_init);
4067
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);
4070                 for (j=0;j<3;j++) {
4071                         match[j].rm_so = -1;
4072                         match[j].rm_eo = -1;
4073                 }
4074                 if (v != NULL && regexec (&mail_list_magic[i].regex, v, 3, match, 0) == 0 && match[1].rm_so != -1) {
4075                         int len1, len2;
4076                         char *mlist;
4077                         
4078                         len1 = match[1].rm_eo - match[1].rm_so;
4079                         len2 = match[2].rm_eo - match[2].rm_so;
4080                         
4081                         mlist = g_malloc (len1 + len2 + 2);
4082                         memcpy (mlist, v + match[1].rm_so, len1);
4083                         if (len2) {
4084                                 mlist[len1] = '@';
4085                                 memcpy (mlist + len1 + 1, v + match[2].rm_so, len2);
4086                                 mlist[len1 + len2 + 1] = '\0';
4087                         } else {
4088                                 mlist[len1] = '\0';
4089                         }
4090                         
4091                         return mlist;
4092                 }
4093         }
4094
4095         return NULL;
4096 }
4097
4098 /* ok, here's the address stuff, what a mess ... */
4099 struct _camel_header_address *
4100 camel_header_address_new (void)
4101 {
4102         struct _camel_header_address *h;
4103         h = g_malloc0(sizeof(*h));
4104         h->type = CAMEL_HEADER_ADDRESS_NONE;
4105         h->refcount = 1;
4106         return h;
4107 }
4108
4109 struct _camel_header_address *
4110 camel_header_address_new_name(const char *name, const char *addr)
4111 {
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);
4117         return h;
4118 }
4119
4120 struct _camel_header_address *
4121 camel_header_address_new_group (const char *name)
4122 {
4123         struct _camel_header_address *h;
4124
4125         h = camel_header_address_new();
4126         h->type = CAMEL_HEADER_ADDRESS_GROUP;
4127         h->name = g_strdup(name);
4128         return h;
4129 }
4130
4131 void
4132 camel_header_address_ref(struct _camel_header_address *h)
4133 {
4134         if (h)
4135                 h->refcount++;
4136 }
4137
4138 void
4139 camel_header_address_unref(struct _camel_header_address *h)
4140 {
4141         if (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) {
4146                                 g_free(h->v.addr);
4147                         }
4148                         g_free(h->name);
4149                         g_free(h);
4150                 } else {
4151                         h->refcount--;
4152                 }
4153         }
4154 }
4155
4156 void
4157 camel_header_address_set_name(struct _camel_header_address *h, const char *name)
4158 {
4159         if (h) {
4160                 g_free(h->name);
4161                 h->name = g_strdup(name);
4162         }
4163 }
4164
4165 void
4166 camel_header_address_set_addr(struct _camel_header_address *h, const char *addr)
4167 {
4168         if (h) {
4169                 if (h->type == CAMEL_HEADER_ADDRESS_NAME
4170                     || h->type == CAMEL_HEADER_ADDRESS_NONE) {
4171                         h->type = CAMEL_HEADER_ADDRESS_NAME;
4172                         g_free(h->v.addr);
4173                         h->v.addr = g_strdup(addr);
4174                 } else {
4175                         g_warning("Trying to set the address on a group");
4176                 }
4177         }
4178 }
4179
4180 void
4181 camel_header_address_set_members(struct _camel_header_address *h, struct _camel_header_address *group)
4182 {
4183         if (h) {
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;
4190                 } else {
4191                         g_warning("Trying to set the members on a name, not group");
4192                 }
4193         }
4194 }
4195
4196 void
4197 camel_header_address_add_member(struct _camel_header_address *h, struct _camel_header_address *member)
4198 {
4199         if (h) {
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);
4204                 }                   
4205         }
4206 }
4207
4208 void
4209 camel_header_address_list_append_list(struct _camel_header_address **l, struct _camel_header_address **h)
4210 {
4211         if (l) {
4212                 struct _camel_header_address *n = (struct _camel_header_address *)l;
4213
4214                 while (n->next)
4215                         n = n->next;
4216                 n->next = *h;
4217         }
4218 }
4219
4220
4221 void
4222 camel_header_address_list_append(struct _camel_header_address **l, struct _camel_header_address *h)
4223 {
4224         if (h) {
4225                 camel_header_address_list_append_list(l, &h);
4226                 h->next = NULL;
4227         }
4228 }
4229
4230 void
4231 camel_header_address_list_clear(struct _camel_header_address **l)
4232 {
4233         struct _camel_header_address *a, *n;
4234         a = *l;
4235         while (a) {
4236                 n = a->next;
4237                 camel_header_address_unref(a);
4238                 a = n;
4239         }
4240         *l = NULL;
4241 }
4242
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) */
4245 static void
4246 header_address_list_encode_append (GString *out, int encode, struct _camel_header_address *a)
4247 {
4248         char *text;
4249         
4250         while (a) {
4251                 switch (a->type) {
4252                 case CAMEL_HEADER_ADDRESS_NAME:
4253                         if (encode)
4254                                 text = camel_header_encode_phrase (a->name);
4255                         else
4256                                 text = a->name;
4257                         if (text && *text)
4258                                 g_string_append_printf (out, "%s <%s>", text, a->v.addr);
4259                         else
4260                                 g_string_append (out, a->v.addr);
4261                         if (encode)
4262                                 g_free (text);
4263                         break;
4264                 case CAMEL_HEADER_ADDRESS_GROUP:
4265                         if (encode)
4266                                 text = camel_header_encode_phrase (a->name);
4267                         else
4268                                 text = 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, ";");
4272                         if (encode)
4273                                 g_free (text);
4274                         break;
4275                 default:
4276                         g_warning ("Invalid address type");
4277                         break;
4278                 }
4279                 a = a->next;
4280                 if (a)
4281                         g_string_append (out, ", ");
4282         }
4283 }
4284
4285 char *
4286 camel_header_address_list_encode (struct _camel_header_address *a)
4287 {
4288         GString *out;
4289         char *ret;
4290         
4291         if (a == NULL)
4292                 return NULL;
4293         
4294         out = g_string_new ("");
4295         header_address_list_encode_append (out, TRUE, a);
4296         ret = out->str;
4297         g_string_free (out, FALSE);
4298         
4299         return ret;
4300 }
4301
4302 char *
4303 camel_header_address_list_format (struct _camel_header_address *a)
4304 {
4305         GString *out;
4306         char *ret;
4307         
4308         if (a == NULL)
4309                 return NULL;
4310         
4311         out = g_string_new ("");
4312         
4313         header_address_list_encode_append (out, FALSE, a);
4314         ret = out->str;
4315         g_string_free (out, FALSE);
4316         
4317         return ret;
4318 }
4319
4320 char *
4321 camel_header_address_fold (const char *in, size_t headerlen)
4322 {
4323         size_t len, outlen;
4324         const char *inptr = in, *space, *p, *n;
4325         GString *out;
4326         char *ret;
4327         int i, needunfold = FALSE;
4328         
4329         if (in == NULL)
4330                 return NULL;
4331         
4332         /* first, check to see if we even need to fold */
4333         len = headerlen + 2;
4334         p = in;
4335         while (*p) {
4336                 n = strchr (p, '\n');
4337                 if (n == NULL) {
4338                         len += strlen (p);
4339                         break;
4340                 }
4341                 
4342                 needunfold = TRUE;
4343                 len += n-p;
4344                 
4345                 if (len >= CAMEL_FOLD_SIZE)
4346                         break;
4347                 len = 0;
4348                 p = n + 1;
4349         }
4350         if (len < CAMEL_FOLD_SIZE)
4351                 return g_strdup (in);
4352         
4353         /* we need to fold, so first unfold (if we need to), then process */
4354         if (needunfold)
4355                 inptr = in = camel_header_unfold (in);
4356         
4357         out = g_string_new ("");
4358         outlen = headerlen + 2;
4359         while (*inptr) {
4360                 space = strchr (inptr, ' ');
4361                 if (space) {
4362                         len = space - inptr + 1;
4363                 } else {
4364                         len = strlen (inptr);
4365                 }
4366                 
4367                 d(printf("next word '%.*s'\n", len, inptr));
4368                 
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");
4375                         outlen = 1;
4376                 }
4377                 
4378                 outlen += len;
4379                 for (i = 0; i < len; i++) {
4380                         g_string_append_c (out, inptr[i]);
4381                 }
4382                 
4383                 inptr += len;
4384         }
4385         ret = out->str;
4386         g_string_free (out, FALSE);
4387         
4388         if (needunfold)
4389                 g_free ((char *)in);
4390         
4391         return ret;     
4392 }
4393
4394 /* simple header folding */
4395 /* will work even if the header is already folded */
4396 char *
4397 camel_header_fold(const char *in, size_t headerlen)
4398 {
4399         size_t len, outlen, i;
4400         const char *inptr = in, *space, *p, *n;
4401         GString *out;
4402         char *ret;
4403         int needunfold = FALSE;
4404
4405         if (in == NULL)
4406                 return NULL;
4407
4408         /* first, check to see if we even need to fold */
4409         len = headerlen + 2;
4410         p = in;
4411         while (*p) {
4412                 n = strchr(p, '\n');
4413                 if (n == NULL) {
4414                         len += strlen (p);
4415                         break;
4416                 }
4417
4418                 needunfold = TRUE;
4419                 len += n-p;
4420                 
4421                 if (len >= CAMEL_FOLD_SIZE)
4422                         break;
4423                 len = 0;
4424                 p = n + 1;
4425         }
4426         if (len < CAMEL_FOLD_SIZE)
4427                 return g_strdup(in);
4428
4429         /* we need to fold, so first unfold (if we need to), then process */
4430         if (needunfold)
4431                 inptr = in = camel_header_unfold(in);
4432
4433         out = g_string_new("");
4434         outlen = headerlen+2;
4435         while (*inptr) {
4436                 space = strchr(inptr, ' ');
4437                 if (space) {
4438                         len = space-inptr+1;
4439                 } else {
4440                         len = strlen(inptr);
4441                 }
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");
4449                         outlen = 1;
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");
4457                                 outlen = 1;
4458                         }
4459                 }
4460                 outlen += len;
4461                 for (i=0;i<len;i++) {
4462                         g_string_append_c(out, inptr[i]);
4463                 }
4464                 inptr += len;
4465         }
4466         ret = out->str;
4467         g_string_free(out, FALSE);
4468
4469         if (needunfold)
4470                 g_free((char *)in);
4471
4472         return ret;     
4473 }
4474
4475 char *
4476 camel_header_unfold(const char *in)
4477 {
4478         char *out = g_malloc(strlen(in)+1);
4479         const char *inptr = in;
4480         char c, *o = out;
4481
4482         o = out;
4483         while ((c = *inptr++)) {
4484                 if (c == '\n') {
4485                         if (camel_mime_is_lwsp(*inptr)) {
4486                                 do {
4487                                         inptr++;
4488                                 } while (camel_mime_is_lwsp(*inptr));
4489                                 *o++ = ' ';
4490                         } else {
4491                                 *o++ = c;
4492                         }
4493                 } else {
4494                         *o++ = c;
4495                 }
4496         }
4497         *o = 0;
4498
4499         return out;
4500 }