considering nuclear weapons
[citadel.git] / libcitadel / lib / mime_parser.c
1 // This is the MIME parser for Citadel.
2 //
3 // Copyright (c) 1998-2023 by the citadel.org development team.
4 //
5 // This program is open source software.  Use, duplication, or disclosure
6 // is subject to the terms of the GNU General Public License, version 3.
7
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <stdio.h>
11 #include <signal.h>
12 #include <sys/types.h>
13 #include <ctype.h>
14 #include <string.h>
15 #include <sys/stat.h>
16 #include <sys/types.h>
17 #include <dirent.h>
18 #include <errno.h>
19
20 #include "xdgmime/xdgmime.h"
21 #include "libcitadel.h"
22 #include "libcitadellocal.h"
23
24 const unsigned char FromHexTable[256] = {
25         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //  0
26         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 10
27         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 20
28         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 30
29         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, // 40
30         0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, // 50
31         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, // 60
32         0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 70
33         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 80
34         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C, // 90
35         0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //100
36         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //110
37         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //120
38         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //130
39         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //140
40         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //150
41         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //160
42         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //170
43         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //180
44         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //190
45         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //200
46         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //210
47         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //220
48         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //230
49         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //240
50         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF                          //250
51 };
52
53
54 long extract_key(char *target, char *source, long sourcelen, char *key, long keylen, char KeyEnd) {
55         char *sptr, *ptr = NULL;
56         int double_quotes = 0;
57         long RealKeyLen = keylen;
58
59         sptr = source;
60
61         while (sptr != NULL) {
62                 ptr = bmstrcasestr_len(sptr, sourcelen - (sptr - source), key, keylen);
63                 if (ptr != NULL) {
64                         while (isspace(*(ptr + RealKeyLen)))
65                                 RealKeyLen ++;
66                         if (*(ptr + RealKeyLen) == KeyEnd) {
67                                 sptr = NULL;
68                                 RealKeyLen ++;                          
69                         }
70                         else {
71                                 sptr = ptr + RealKeyLen + 1;
72                         }
73                 }
74                 else 
75                         sptr = ptr;
76         }
77         if (ptr == NULL) {
78                 *target = '\0';
79                 return 0;
80         }
81         strcpy(target, (ptr + RealKeyLen));
82
83         for (ptr=target; (*ptr != 0); ptr++) {
84
85                 // A semicolon means we've hit the end of the key, unless we're inside double quotes
86                 if ( (double_quotes != 1) && (*ptr == ';')) {
87                         *ptr = 0;
88                 }
89
90                 // if we find double quotes, we've got a great set of string boundaries
91                 if (*ptr == '\"') {
92                         ++double_quotes;
93                         if (double_quotes == 1) {
94                                 strcpy(ptr, ptr+1);
95                         }
96                         else {
97                                 *ptr = 0;
98                         }
99                 }
100         }
101         *ptr = '\0';
102         return ptr - target;
103 }
104
105
106 // For non-multipart messages, we need to generate a quickie partnum of "1"
107 // to return to callback functions.  Some callbacks demand it.
108 char *fixed_partnum(char *supplied_partnum) {
109         if (supplied_partnum == NULL) return "1";
110         if (strlen(supplied_partnum)==0) return "1";
111         return supplied_partnum;
112 }
113
114
115 static inline unsigned int _decode_hex(const char *Source) {
116         unsigned int ret = '?';
117         unsigned char LO_NIBBLE;
118         unsigned char HI_NIBBLE;
119
120         HI_NIBBLE = FromHexTable[(unsigned char) *Source];
121         LO_NIBBLE = FromHexTable[(unsigned char) *(Source+1)];
122         
123         if ((LO_NIBBLE == 0xFF) || (LO_NIBBLE == 0xFF))
124                 return ret;
125         ret = HI_NIBBLE;
126         ret = ret << 4;
127         ret = ret | LO_NIBBLE;
128         return ret;
129 }
130
131 unsigned int decode_hex(char *Source) {return _decode_hex(Source);}
132
133
134 // Convert "quoted-printable" to binary.  Returns number of bytes decoded.
135 // according to RFC2045 section 6.7
136 int CtdlDecodeQuotedPrintable(char *decoded, char *encoded, int sourcelen) {
137         unsigned int ch;
138         int decoded_length = 0;
139         int pos = 0;
140
141         while (pos < sourcelen) {
142                 if (*(encoded + pos) == '=') {
143                         pos ++;
144                         if (*(encoded + pos) == '\n') {
145                                 pos ++;
146                         }
147                         else if (*(encoded + pos) == '\r') {
148                                 pos ++;
149                                 if (*(encoded + pos) == '\n')
150                                         pos++;
151                         }
152                         else {
153                                 ch = _decode_hex(&encoded[pos]);
154                                 pos += 2;
155                                 decoded[decoded_length++] = ch;
156                         }
157                 }
158                 else {
159                         decoded[decoded_length++] = encoded[pos];
160                         pos += 1;
161                 }
162         }
163         decoded[decoded_length] = 0;
164         return(decoded_length);
165 }
166
167
168 // Given a message or message-part body and a length, handle any necessary
169 // decoding and pass the request up the stack.
170 void mime_decode(char *partnum,
171                  char *part_start, size_t length,
172                  char *content_type, char *charset, char *encoding,
173                  char *disposition,
174                  char *id,
175                  char *name, char *filename,
176                  MimeParserCallBackType CallBack,
177                  MimeParserCallBackType PreMultiPartCallBack,
178                  MimeParserCallBackType PostMultiPartCallBack,
179                  void *userdata,
180                  int dont_decode
181 ) {
182         char *decoded;
183         size_t bytes_decoded = 0;
184
185         // Some encodings aren't really encodings
186         if (!strcasecmp(encoding, "7bit"))
187                 *encoding = '\0';
188         if (!strcasecmp(encoding, "8bit"))
189                 *encoding = '\0';
190         if (!strcasecmp(encoding, "binary"))
191                 *encoding = '\0';
192         if (!strcasecmp(encoding, "ISO-8859-1"))
193                 *encoding = '\0';
194
195         // If this part is not encoded, send as-is
196         if ( (strlen(encoding) == 0) || (dont_decode)) {
197                 if (CallBack != NULL) {
198                         CallBack(name, 
199                                  filename, 
200                                  fixed_partnum(partnum),
201                                  disposition, 
202                                  part_start,
203                                  content_type, 
204                                  charset, 
205                                  length, 
206                                  encoding, 
207                                  id,
208                                  userdata);
209                         }
210                 return;
211         }
212         
213         // Fail silently if we hit an unknown encoding.
214         if ((strcasecmp(encoding, "base64")) && (strcasecmp(encoding, "quoted-printable"))) {
215                 return;
216         }
217
218         // Allocate a buffer for the decoded data.  The output buffer is slightly
219         // larger than the input buffer; this assumes that the decoded data
220         // will never be significantly larger than the encoded data.  This is a
221         // safe assumption with base64, uuencode, and quoted-printable.
222         decoded = malloc(length + 32768);
223         if (decoded == NULL) {
224                 return;
225         }
226
227         if (!strcasecmp(encoding, "base64")) {
228                 bytes_decoded = CtdlDecodeBase64(decoded, part_start, length);
229         }
230         else if (!strcasecmp(encoding, "quoted-printable")) {
231                 bytes_decoded = CtdlDecodeQuotedPrintable(decoded, part_start, length);
232         }
233
234         if (bytes_decoded > 0) if (CallBack != NULL) {
235                         char encoding_buf[SIZ];
236
237                         strcpy(encoding_buf, "binary");
238                         CallBack(name, 
239                                  filename, 
240                                  fixed_partnum(partnum),
241                                  disposition, 
242                                  decoded,
243                                  content_type, 
244                                  charset, 
245                                  bytes_decoded, 
246                                  encoding_buf, 
247                                  id, 
248                                  userdata);
249         }
250
251         free(decoded);
252 }
253
254
255 // this is a bastardization of mime_decode() which can be called if 'dont_decode' was set; 
256 // to postpone the cpu intense process of decoding until the time when it realy wants the content. 
257 // returns: 
258 //   - > 0 we decoded something, its on *decoded, you need to free it.
259 //   - = 0 no need to decode stuff. *decoded will be NULL.
260 //   - < 0 an error occured, either an unknown encoding, or alloc failed. no need to free.
261 int mime_decode_now (char *part_start, 
262                      size_t length,
263                      char *encoding,
264                      char **decoded,
265                      size_t *bytes_decoded)
266 {
267         *bytes_decoded = 0;
268         *decoded = NULL;
269         // Some encodings aren't really encodings
270         if (!strcasecmp(encoding, "7bit"))
271                 *encoding = '\0';
272         if (!strcasecmp(encoding, "8bit"))
273                 *encoding = '\0';
274         if (!strcasecmp(encoding, "binary"))
275                 *encoding = '\0';
276
277         // If this part is not encoded, send as-is
278         if (strlen(encoding) == 0) {
279                 return 0;
280         }
281
282         // Fail if we hit an unknown encoding.
283         if ((strcasecmp(encoding, "base64")) && (strcasecmp(encoding, "quoted-printable"))) {
284                 return -1;
285         }
286
287         // Allocate a buffer for the decoded data.  The output buffer is slightly
288         // larger than the input buffer; this assumes that the decoded data
289         // will never be significantly larger than the encoded data.  This is a
290         // safe assumption with base64, uuencode, and quoted-printable.
291         *decoded = malloc(length + 32768);
292         if (decoded == NULL) {
293                 return -1;
294         }
295
296         if (!strcasecmp(encoding, "base64")) {
297                 *bytes_decoded = CtdlDecodeBase64(*decoded, part_start, length);
298                 return 1;
299         }
300         else if (!strcasecmp(encoding, "quoted-printable")) {
301                 *bytes_decoded = CtdlDecodeQuotedPrintable(*decoded, part_start, length);
302                 return 1;
303         }
304         return -1;
305 }
306
307 typedef enum _eIntMimeHdrs {
308         boundary,
309         startary,
310         endary,
311         content_type,
312         charset,
313         encoding,
314         content_type_name,
315         content_disposition_name,
316         filename,
317         disposition,
318         id,
319         eMax // don't move!
320 } eIntMimeHdrs;
321
322 typedef struct _CBufStr {
323         char Key[SIZ];
324         long len;
325 }CBufStr;
326
327 typedef struct _interesting_mime_headers {
328         CBufStr b[eMax];
329         long content_length;
330         long is_multipart;
331 } interesting_mime_headers;
332
333
334 static void FlushInterestingMimes(interesting_mime_headers *m) {
335         int i;
336         
337         for (i = 0; i < eMax; i++) {
338                 m->b[i].Key[0] = '\0';
339                 m->b[i].len = 0;
340         }
341         m->content_length = -1;
342 }
343
344
345 static interesting_mime_headers *InitInterestingMimes(void) {
346         interesting_mime_headers *m;
347         m = (interesting_mime_headers*) malloc( sizeof(interesting_mime_headers));
348
349         FlushInterestingMimes(m);
350
351         return m;
352 }
353
354
355 static long parse_MimeHeaders(interesting_mime_headers *m, char** pcontent_start, char *content_end) {
356         char buf[SIZ];
357         char header[SIZ];
358         long headerlen;
359         char *ptr, *pch;
360         int buflen = 0;
361         int i;
362
363         // Learn interesting things from the headers
364         ptr = *pcontent_start;
365         *header = '\0';
366         headerlen = 0;
367         do {
368                 ptr = memreadlinelen(ptr, buf, SIZ, &buflen);
369
370                 for (i = 0; i < buflen; ++i) {
371                         if (isspace(buf[i])) {
372                                 buf[i] = ' ';
373                         }
374                 }
375
376                 if (!isspace(buf[0]) && (headerlen > 0)) {
377                         if (!strncasecmp(header, "Content-type:", 13)) {
378                                 memcpy (m->b[content_type].Key, &header[13], headerlen - 12);
379                                 m->b[content_type].Key[headerlen - 12] = '\0';
380                                 m->b[content_type].len = string_trim (m->b[content_type].Key);
381
382                                 m->b[content_type_name].len = extract_key(m->b[content_type_name].Key, CKEY(m->b[content_type]), HKEY("name"), '=');
383                                 m->b[charset].len           = extract_key(m->b[charset].Key,           CKEY(m->b[content_type]), HKEY("charset"), '=');
384                                 m->b[boundary].len          = extract_key(m->b[boundary].Key,          header,       headerlen,  HKEY("boundary"), '=');
385
386                                 // Deal with weird headers
387                                 pch = strchr(m->b[content_type].Key, ' ');
388                                 if (pch != NULL) {
389                                         *pch = '\0';
390                                         m->b[content_type].len = m->b[content_type].Key - pch;
391                                 }
392                                 pch = strchr(m->b[content_type].Key, ';');
393                                 if (pch != NULL) {
394                                         *pch = '\0';
395                                         m->b[content_type].len = m->b[content_type].Key - pch;
396                                 }
397                         }
398                         else if (!strncasecmp(header, "Content-Disposition:", 20)) {
399                                 memcpy (m->b[disposition].Key, &header[20], headerlen - 19);
400                                 m->b[disposition].Key[headerlen - 19] = '\0';
401                                 m->b[disposition].len = string_trim(m->b[disposition].Key);
402
403                                 m->b[content_disposition_name].len = extract_key(m->b[content_disposition_name].Key, CKEY(m->b[disposition]), HKEY("name"), '=');
404                                 m->b[filename].len                 = extract_key(m->b[filename].Key,                 CKEY(m->b[disposition]), HKEY("filename"), '=');
405                                 pch = strchr(m->b[disposition].Key, ';');
406                                 if (pch != NULL) *pch = '\0';
407                                 m->b[disposition].len = string_trim(m->b[disposition].Key);
408                         }
409                         else if (!strncasecmp(header, "Content-ID:", 11)) {
410                                 memcpy(m->b[id].Key, &header[11], headerlen - 11);
411                                 m->b[id].Key[headerlen - 11] = '\0';
412                                 string_trim(m->b[id].Key);
413                                 m->b[id].len = stripallbut(m->b[id].Key, '<', '>');
414                         }
415                         else if (!strncasecmp(header, "Content-length: ", 15)) {
416                                 char *clbuf;
417                                 clbuf = &header[15];
418                                 while (isspace(*clbuf))
419                                         clbuf ++;
420                                 m->content_length = (size_t) atol(clbuf);
421                         }
422                         else if (!strncasecmp(header, "Content-transfer-encoding: ", 26)) {
423                                 memcpy(m->b[encoding].Key, &header[26], headerlen - 26);
424                                 m->b[encoding].Key[headerlen - 26] = '\0';
425                                 m->b[encoding].len = string_trim(m->b[encoding].Key);
426                         }
427                         *header = '\0';
428                         headerlen = 0;
429                 }
430                 if ((headerlen + buflen + 2) < SIZ) {
431                         memcpy(&header[headerlen], buf, buflen);
432                         headerlen += buflen;
433                         header[headerlen] = '\0';
434                 }
435                 if (ptr >= content_end) {
436                         return -1;
437                 }
438         } while ((!IsEmptyStr(buf)) && (*ptr != 0));
439
440         m->is_multipart = m->b[boundary].len != 0;
441         *pcontent_start = ptr;
442
443         return 0;
444 }
445
446
447 static int IsAsciiEncoding(interesting_mime_headers *m) {
448
449         if ((m->b[encoding].len != 0) && (strcasecmp(m->b[encoding].Key, "base64") == 0)) {
450                 return 1;
451         }
452         if ((m->b[encoding].len != 0) && (strcmp(m->b[encoding].Key, "quoted-printable") == 0)) {
453                 return 1;
454         }
455
456         return 0;
457 }
458
459
460 static char *FindNextContent(char *ptr,
461                         char *content_end,
462                         interesting_mime_headers *SubMimeHeaders,
463                         interesting_mime_headers *m)
464 {
465         char *next_boundary;
466         char  tmp;
467
468         if (IsAsciiEncoding(SubMimeHeaders)) {
469                 tmp = *content_end;
470                 *content_end = '\0';
471
472                 // ok, if we have a content length of the mime part, 
473                 // try skipping the content on the search for the next
474                 // boundary. since we don't trust the content_length
475                 // to be all accurate, and suspect it to lose one digit 
476                 // per line with a line length of 80 chars, we need 
477                 // to start searching a little before..
478
479                 if ((SubMimeHeaders->content_length != -1) && (SubMimeHeaders->content_length > 10)) {
480                         char *pptr;
481                         long lines;
482                                         
483                         lines = SubMimeHeaders->content_length / 80;
484                         pptr = ptr + SubMimeHeaders->content_length - lines - 10;
485                         if (pptr < content_end)
486                                 ptr = pptr;
487                 }
488                         
489                 next_boundary = strstr(ptr, m->b[startary].Key);
490                 *content_end = tmp;
491         }
492         else {
493                 char *srch;
494                 // ok, if we have a content length of the mime part, 
495                 // try skipping the content on the search for the next
496                 // boundary. since we don't trust the content_length
497                 // to be all accurate, start searching a little before..
498
499                 if ((SubMimeHeaders->content_length != -1) && (SubMimeHeaders->content_length > 10)) {
500                         char *pptr;
501                         pptr = ptr + SubMimeHeaders->content_length - 10;
502                         if (pptr < content_end)
503                                 ptr = pptr;
504                 }
505                 
506                 srch = next_boundary = NULL;
507                 for (srch = memchr(ptr, '-',  content_end - ptr);
508                         (srch != NULL) && (srch < content_end); 
509                         srch = memchr(srch, '-',  content_end - srch)) 
510                 {
511                         if (!memcmp(srch, m->b[startary].Key, m->b[startary].len)) {
512                                 next_boundary = srch;
513                                 srch = content_end;
514                         }
515                         else srch ++;
516
517                 }
518
519         }
520         return next_boundary;
521 }
522
523
524 // Break out the components of a multipart message
525 // (This function expects to be fed HEADERS + CONTENT)
526 // Note: NULL can be supplied as content_end; in this case, the message is
527 // considered to have ended when the parser encounters a 0x00 byte.
528 static void recurseable_mime_parser(char *partnum,
529                 char *content_start, char *content_end,
530                 MimeParserCallBackType CallBack,
531                 MimeParserCallBackType PreMultiPartCallBack,
532                 MimeParserCallBackType PostMultiPartCallBack,
533                 void *userdata,
534                 int dont_decode, 
535                 interesting_mime_headers *m
536 ) {
537         interesting_mime_headers *SubMimeHeaders;
538         char     *ptr;
539         char     *part_start;
540         char     *part_end = NULL;
541         char     *evaluate_crlf_ptr = NULL;
542         char     *next_boundary;
543         char      nested_partnum[256];
544         int       crlf_in_use = 0;
545         int       part_seq = 0;
546         CBufStr  *chosen_name;
547
548         // If this is a multipart message, then recursively process it
549         ptr = content_start;
550         part_start = NULL;
551         if (m->is_multipart) {
552
553                 // Tell the client about this message's multipartedness
554                 if (PreMultiPartCallBack != NULL) {
555                         PreMultiPartCallBack("", 
556                                 "", 
557                                 partnum, 
558                                 "",
559                                 NULL, 
560                                 m->b[content_type].Key, 
561                                 m->b[charset].Key,
562                                 0, 
563                                 m->b[encoding].Key, 
564                                 m->b[id].Key, 
565                                 userdata
566                         );
567                 }
568
569                 // Figure out where the boundaries are
570                 m->b[startary].len = snprintf(m->b[startary].Key, SIZ, "--%s", m->b[boundary].Key);
571                 SubMimeHeaders = InitInterestingMimes ();
572
573                 while ((*ptr == '\r') || (*ptr == '\n')) {
574                         ptr++;
575                 }
576
577                 if (strncmp(ptr, m->b[startary].Key, m->b[startary].len) == 0) {
578                         ptr += m->b[startary].len;
579                 }
580
581                 while ((*ptr == '\r') || (*ptr == '\n')) {
582                         ptr ++;
583                 }
584
585                 part_start = NULL;
586                 do {
587                         char *optr;
588
589                         optr = ptr;
590                         if (parse_MimeHeaders(SubMimeHeaders, &ptr, content_end) != 0)
591                                 break;
592                         if ((ptr - optr > 2) && (*(ptr - 2) == '\r')) {
593                                 crlf_in_use = 1;
594                         }
595                         
596                         part_start = ptr;
597                         
598                         next_boundary = FindNextContent(ptr, content_end, SubMimeHeaders, m);
599                         if ((next_boundary != NULL) && (next_boundary - part_start < 3)) {
600                                 FlushInterestingMimes(SubMimeHeaders);
601                                 continue;
602                         }
603
604                         if ( (part_start != NULL) && (next_boundary != NULL) ) {
605                                 part_end = next_boundary;
606                                 --part_end;             // omit the trailing LF
607                                 if (crlf_in_use) {
608                                         --part_end;     // omit the trailing CR
609                                 }
610
611                                 if (!IsEmptyStr(partnum)) {
612                                         snprintf(nested_partnum,
613                                                  sizeof nested_partnum,
614                                                  "%s.%d", partnum,
615                                                  ++part_seq);
616                                 }
617                                 else {
618                                         snprintf(nested_partnum,
619                                                  sizeof nested_partnum,
620                                                  "%d", ++part_seq);
621                                 }
622                                 recurseable_mime_parser(nested_partnum,
623                                                         part_start, 
624                                                         part_end,
625                                                         CallBack,
626                                                         PreMultiPartCallBack,
627                                                         PostMultiPartCallBack,
628                                                         userdata,
629                                                         dont_decode, 
630                                                         SubMimeHeaders);
631                         }
632
633                         if (next_boundary != NULL) {
634                                 // If we pass out of scope, don't attempt to read past the end boundary.
635                                 if ((*(next_boundary + m->b[startary].len) == '-') && 
636                                     (*(next_boundary + m->b[startary].len + 1) == '-') ){
637                                         ptr = content_end;
638                                 }
639                                 else {
640                                         // Set up for the next part.
641                                         part_start = strstr(next_boundary, "\n");
642                                         
643                                         // Determine whether newlines are LF or CRLF
644                                         evaluate_crlf_ptr = part_start;
645                                         --evaluate_crlf_ptr;
646                                         if ((*evaluate_crlf_ptr == '\r') && (*(evaluate_crlf_ptr + 1) == '\n')) {
647                                                 crlf_in_use = 1;
648                                         }
649                                         else {
650                                                 crlf_in_use = 0;
651                                         }
652
653                                         // Advance past the LF ... now we're in the next part
654                                         ++part_start;
655                                         ptr = part_start;
656                                 }
657                         }
658                         else {
659                                 // Invalid end of multipart.  Bail out!
660                                 ptr = content_end;
661                         }
662                         FlushInterestingMimes(SubMimeHeaders);
663                 } while ( (ptr < content_end) && (next_boundary != NULL) );
664
665                 free(SubMimeHeaders);
666
667                 if (PostMultiPartCallBack != NULL) {
668                         PostMultiPartCallBack("", 
669                                 "", 
670                                 partnum, 
671                                 "", 
672                                 NULL,
673                                 m->b[content_type].Key, 
674                                 m->b[charset].Key,
675                                 0, 
676                                 m->b[encoding].Key, 
677                                 m->b[id].Key, 
678                                 userdata
679                         );
680                 }
681         }
682         // If it's not a multipart message, then do something with it
683         else {
684                 size_t length;
685                 part_start = ptr;
686                 length = content_end - part_start;
687                 ptr = part_end = content_end;
688
689                 // The following code will truncate the MIME part to the size
690                 // specified by the Content-length: header.   We have commented it
691                 // out because these headers have a tendency to be wrong.
692                 //
693                 //      if ( (content_length > 0) && (length > content_length) ) {
694                 //              length = content_length;
695                 //      }
696
697                 // Sometimes the "name" field is tacked on to Content-type,
698                 // and sometimes it's tacked on to Content-disposition.  Use
699                 // whichever one we have.
700                 if (m->b[content_disposition_name].len > m->b[content_type_name].len) {
701                         chosen_name = &m->b[content_disposition_name];
702                 }
703                 else {
704                         chosen_name = &m->b[content_type_name];
705                 }
706         
707                 // Ok, we've got a non-multipart part here, so do something with it.
708                 mime_decode(partnum,
709                                 part_start, 
710                                 length,
711                                 m->b[content_type].Key, 
712                                 m->b[charset].Key,
713                                 m->b[encoding].Key, 
714                                 m->b[disposition].Key, 
715                                 m->b[id].Key, 
716                                 chosen_name->Key, 
717                                 m->b[filename].Key,
718                                 CallBack, 
719                                 NULL,
720                                 NULL,
721                                 userdata, 
722                                 dont_decode
723                         );
724
725                 // Now if it's an encapsulated message/rfc822 then we have to recurse into it
726                 if (!strcasecmp(&m->b[content_type].Key[0], "message/rfc822")) {
727
728                         if (PreMultiPartCallBack != NULL) {
729                                 PreMultiPartCallBack("", 
730                                                          "", 
731                                                          partnum, 
732                                                          "",
733                                                          NULL, 
734                                                          m->b[content_type].Key, 
735                                                          m->b[charset].Key,
736                                                          0, 
737                                                          m->b[encoding].Key, 
738                                                          m->b[id].Key, 
739                                                          userdata);
740                         }
741                         if (CallBack != NULL) {
742                                 if (strlen(partnum) > 0) {
743                                         snprintf(nested_partnum,
744                                                  sizeof nested_partnum,
745                                                  "%s.%d", partnum,
746                                                  ++part_seq);
747                                 }
748                                 else {
749                                         snprintf(nested_partnum,
750                                                  sizeof nested_partnum,
751                                                  "%d", ++part_seq);
752                                 }
753                                 the_mime_parser(nested_partnum,
754                                                 part_start, 
755                                                 part_end,
756                                                 CallBack,
757                                                 PreMultiPartCallBack,
758                                                 PostMultiPartCallBack,
759                                                 userdata,
760                                                 dont_decode
761                                         );
762                         }
763                         if (PostMultiPartCallBack != NULL) {
764                                 PostMultiPartCallBack("", 
765                                         "", 
766                                         partnum, 
767                                         "", 
768                                         NULL,
769                                         m->b[content_type].Key, 
770                                         m->b[charset].Key,
771                                         0, 
772                                         m->b[encoding].Key, 
773                                         m->b[id].Key, 
774                                         userdata
775                                 );
776                         }
777
778
779                 }
780
781         }
782
783 }
784
785
786 // Break out the components of a multipart message
787 // (This function expects to be fed HEADERS + CONTENT)
788 // Note: NULL can be supplied as content_end; in this case, the message is
789 // considered to have ended when the parser encounters a 0x00 byte.
790 void the_mime_parser(char *partnum,
791                      char *content_start, char *content_end,
792                      MimeParserCallBackType CallBack,
793                      MimeParserCallBackType PreMultiPartCallBack,
794                      MimeParserCallBackType PostMultiPartCallBack,
795                      void *userdata,
796                      int dont_decode)
797 {
798         interesting_mime_headers *m;
799
800         // If the caller didn't supply an endpointer, generate one by measure
801         if (content_end == NULL) {
802                 content_end = &content_start[strlen(content_start)];
803         }
804
805         m = InitInterestingMimes();
806
807         if (!parse_MimeHeaders(m, &content_start, content_end)) {
808
809                 recurseable_mime_parser(partnum,
810                                         content_start, content_end,
811                                         CallBack,
812                                         PreMultiPartCallBack,
813                                         PostMultiPartCallBack,
814                                         userdata,
815                                         dont_decode,
816                                         m);
817         }
818         free(m);
819 }
820
821
822 // Entry point for the MIME parser.
823 // (This function expects to be fed HEADERS + CONTENT)
824 // Note: NULL can be supplied as content_end; in this case, the message is
825 // considered to have ended when the parser encounters a 0x00 byte.
826 void mime_parser(char *content_start,
827                  char *content_end,
828                  MimeParserCallBackType CallBack,
829                  MimeParserCallBackType PreMultiPartCallBack,
830                  MimeParserCallBackType PostMultiPartCallBack,
831                  void *userdata,
832                  int dont_decode)
833 {
834         the_mime_parser("", content_start, content_end,
835                         CallBack,
836                         PreMultiPartCallBack,
837                         PostMultiPartCallBack,
838                         userdata, dont_decode);
839 }
840
841
842 typedef struct _MimeGuess {
843         const char *Pattern;
844         size_t PatternLen;
845         long PatternOffset;
846         const char *MimeString;
847 } MimeGuess;
848
849 MimeGuess MyMimes [] = {
850         {
851                 "GIF",
852                 3,
853                 0,
854                 "image/gif"
855         },
856         {
857                 "\xff\xd8",
858                 2,
859                 0,
860                 "image/jpeg"
861         },
862         {
863                 "\x89PNG",
864                 4,
865                 0,
866                 "image/png"
867         },
868         { // last...
869                 "",
870                 0,
871                 0,
872                 ""
873         }
874 };
875
876
877 const char *GuessMimeType(const char *data, size_t dlen) {
878         int MimeIndex = 0;
879
880         while (MyMimes[MimeIndex].PatternLen != 0) {
881                 if ((MyMimes[MimeIndex].PatternLen + 
882                      MyMimes[MimeIndex].PatternOffset < dlen) &&
883                     strncmp(MyMimes[MimeIndex].Pattern, 
884                             &data[MyMimes[MimeIndex].PatternOffset], 
885                             MyMimes[MimeIndex].PatternLen) == 0)
886                 {
887                         return MyMimes[MimeIndex].MimeString;
888                 }
889                 MimeIndex ++;
890         }
891         // ok, our simple minded algorythm didn't find anything, 
892         // let the big chegger try it, he wil default to application/octet-stream
893         return (xdg_mime_get_mime_type_for_data(data, dlen));
894 }
895
896
897 const char* GuessMimeByFilename(const char *what, size_t len) {
898         // we know some hardcoded on our own, try them...
899         if ((len > 3) && !strncasecmp(&what[len - 4], ".gif", 4))
900                 return "image/gif";
901         else if ((len > 2) && !strncasecmp(&what[len - 3], ".js", 3))
902                 return  "text/javascript";
903         else if ((len > 3) && !strncasecmp(&what[len - 4], ".txt", 4))
904                 return "text/plain";
905         else if ((len > 3) && !strncasecmp(&what[len - 4], ".css", 4))
906                 return "text/css";
907         else if ((len > 3) && !strncasecmp(&what[len - 4], ".htc", 4))
908                 return "text/x-component";
909         else if ((len > 3) && !strncasecmp(&what[len - 4], ".jpg", 4))
910                 return "image/jpeg";
911         else if ((len > 4) && !strncasecmp(&what[len - 5], ".jpeg", 5))
912                 return "image/jpeg";
913         else if ((len > 3) && !strncasecmp(&what[len - 4], ".png", 4))
914                 return "image/png";
915         else if ((len > 3) && !strncasecmp(&what[len - 4], ".ico", 4))
916                 return "image/x-icon";
917         else if ((len > 3) && !strncasecmp(&what[len - 4], ".vcf", 4))
918                 return "text/x-vcard";
919         else if ((len > 4) && !strncasecmp(&what[len - 5], ".html", 5))
920                 return "text/html";
921         else if ((len > 3) && !strncasecmp(&what[len - 4], ".htm", 4))
922                 return "text/html";
923         else if ((len > 3) && !strncasecmp(&what[len - 4], ".wml", 4))
924                 return "text/vnd.wap.wml";
925         else if ((len > 4) && !strncasecmp(&what[len - 5], ".wmls", 5))
926                 return "text/vnd.wap.wmlscript";
927         else if ((len > 4) && !strncasecmp(&what[len - 5], ".wmlc", 5))
928                 return "application/vnd.wap.wmlc";
929         else if ((len > 5) && !strncasecmp(&what[len - 6], ".wmlsc", 6))
930                 return "application/vnd.wap.wmlscriptc";
931         else if ((len > 4) && !strncasecmp(&what[len - 5], ".wbmp", 5))
932                 return "image/vnd.wap.wbmp";
933         else
934                 // and let xdgmime do the fallback.
935                 return xdg_mime_get_mime_type_from_file_name(what);
936 }
937
938 static HashList *IconHash = NULL;
939
940 typedef struct IconName IconName;
941
942 struct IconName {
943         char *FlatName;
944         char *FileName;
945 };
946
947
948 static void DeleteIcon(void *IconNamePtr) {
949         IconName *Icon = (IconName*) IconNamePtr;
950         free(Icon->FlatName);
951         free(Icon->FileName);
952         free(Icon);
953 }
954
955
956 #define GENSTR "x-generic"
957 #define IGNORE_PREFIX_1 "gnome-mime"
958 int LoadIconDir(const char *DirName) {
959         DIR *filedir = NULL;
960         struct dirent *filedir_entry;
961         int d_namelen;
962         int d_without_ext;
963         IconName *Icon;
964
965         filedir = opendir (DirName);
966         IconHash = NewHash(1, NULL);
967         if (filedir == NULL) {
968                 return 0;
969         }
970
971         while ((filedir_entry = readdir(filedir))) {
972                 char *MinorPtr;
973                 char *PStart;
974 #ifdef _DIRENT_HAVE_D_NAMLEN
975                 d_namelen = filedir_entry->d_namlen;
976 #else
977                 d_namelen = strlen(filedir_entry->d_name);
978 #endif
979                 d_without_ext = d_namelen;
980                 while ((d_without_ext > 0) && (filedir_entry->d_name[d_without_ext] != '.'))
981                         d_without_ext --;
982                 if ((d_without_ext == 0) || (d_namelen < 3))
983                         continue;
984
985                 if ((sizeof(IGNORE_PREFIX_1) < d_namelen) &&
986                     (strncmp(IGNORE_PREFIX_1, 
987                              filedir_entry->d_name, 
988                              sizeof(IGNORE_PREFIX_1) - 1) == 0)) {
989                         PStart = filedir_entry->d_name + sizeof(IGNORE_PREFIX_1);
990                         d_without_ext -= sizeof(IGNORE_PREFIX_1);
991                 }
992                 else {
993                         PStart = filedir_entry->d_name;
994                 }
995                 Icon = malloc(sizeof(IconName));
996
997                 Icon->FileName = malloc(d_namelen + 1);
998                 memcpy(Icon->FileName, filedir_entry->d_name, d_namelen + 1);
999
1000                 Icon->FlatName = malloc(d_without_ext + 1);
1001                 memcpy(Icon->FlatName, PStart, d_without_ext);
1002                 Icon->FlatName[d_without_ext] = '\0';
1003                 // Try to find Minor type in image-jpeg
1004                 MinorPtr = strchr(Icon->FlatName, '-');
1005                 if (MinorPtr != NULL) {
1006                         size_t MinorLen;
1007                         MinorLen = 1 + d_without_ext - (MinorPtr - Icon->FlatName + 1);
1008                         if ((MinorLen == sizeof(GENSTR)) && 
1009                             (strncmp(MinorPtr + 1, GENSTR, sizeof(GENSTR)) == 0)) {
1010                                 // ok, we found a generic filename. cut the generic.
1011                                 *MinorPtr = '\0';
1012                                 d_without_ext = d_without_ext - (MinorPtr - Icon->FlatName);
1013                         }
1014                         else { // Map the major / minor separator to /
1015                                 *MinorPtr = '/';
1016                         }
1017                 }
1018
1019                 Put(IconHash, Icon->FlatName, d_without_ext, Icon, DeleteIcon);
1020         }
1021         closedir(filedir);
1022         return 1;
1023 }
1024
1025
1026 const char *GetIconFilename(char *MimeType, size_t len) {
1027         void *vIcon;
1028         IconName *Icon;
1029         
1030         if (IconHash == NULL) {
1031                 return NULL;
1032         }
1033
1034         GetHash(IconHash, MimeType, len, &vIcon), Icon = (IconName*) vIcon;
1035         // didn't find the exact mimetype? try major only.
1036         if (Icon == NULL) {
1037                 char * pMinor;
1038                 pMinor = strchr(MimeType, '/');
1039                 if (pMinor != NULL) {
1040                         *pMinor = '\0';
1041                         GetHash(IconHash, MimeType, pMinor - MimeType, &vIcon),
1042                                 Icon = (IconName*) vIcon;
1043                 }
1044         }
1045         if (Icon == NULL) {
1046                 return NULL;
1047         }
1048
1049         return Icon->FileName;
1050 }
1051
1052
1053 void ShutDownLibCitadelMime(void) {
1054         DeleteHash(&IconHash);
1055 }