moved whitespace around
[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(
461                 char *ptr,
462                 char *content_end,
463                 interesting_mime_headers *SubMimeHeaders,
464                 interesting_mime_headers *m
465 ) {
466         char *next_boundary;
467         char tmp;
468
469         if (IsAsciiEncoding(SubMimeHeaders)) {
470                 tmp = *content_end;
471                 *content_end = '\0';
472
473                 // ok, if we have a content length of the mime part, 
474                 // try skipping the content on the search for the next
475                 // boundary. since we don't trust the content_length
476                 // to be all accurate, and suspect it to lose one digit 
477                 // per line with a line length of 80 chars, we need 
478                 // to start searching a little before..
479
480                 if ((SubMimeHeaders->content_length != -1) && (SubMimeHeaders->content_length > 10)) {
481                         char *pptr;
482                         long lines;
483                                         
484                         lines = SubMimeHeaders->content_length / 80;
485                         pptr = ptr + SubMimeHeaders->content_length - lines - 10;
486                         if (pptr < content_end) {
487                                 ptr = pptr;
488                         }
489                 }
490                         
491                 next_boundary = strstr(ptr, m->b[startary].Key);
492                 *content_end = tmp;
493         }
494         else {
495                 char *srch;
496                 // ok, if we have a content length of the mime part, 
497                 // try skipping the content on the search for the next
498                 // boundary. since we don't trust the content_length
499                 // to be all accurate, start searching a little before..
500
501                 if ((SubMimeHeaders->content_length != -1) && (SubMimeHeaders->content_length > 10)) {
502                         char *pptr;
503                         pptr = ptr + SubMimeHeaders->content_length - 10;
504                         if (pptr < content_end)
505                                 ptr = pptr;
506                 }
507                 
508                 srch = next_boundary = NULL;
509                 for (srch = memchr(ptr, '-',  content_end - ptr);
510                         (srch != NULL) && (srch < content_end); 
511                         srch = memchr(srch, '-',  content_end - srch)) 
512                 {
513                         if (!memcmp(srch, m->b[startary].Key, m->b[startary].len)) {
514                                 next_boundary = srch;
515                                 srch = content_end;
516                         }
517                         else {
518                                 srch++;
519                         }
520                 }
521
522         }
523         return next_boundary;
524 }
525
526
527 // Break out the components of a multipart message
528 // (This function expects to be fed HEADERS + CONTENT)
529 // Note: NULL can be supplied as content_end; in this case, the message is
530 // considered to have ended when the parser encounters a 0x00 byte.
531 static void recurseable_mime_parser(char *partnum,
532                 char *content_start, char *content_end,
533                 MimeParserCallBackType CallBack,
534                 MimeParserCallBackType PreMultiPartCallBack,
535                 MimeParserCallBackType PostMultiPartCallBack,
536                 void *userdata,
537                 int dont_decode, 
538                 interesting_mime_headers *m
539 ) {
540         interesting_mime_headers *SubMimeHeaders;
541         char *ptr;
542         char *part_start;
543         char *part_end = NULL;
544         char *evaluate_crlf_ptr = NULL;
545         char *next_boundary;
546         char nested_partnum[256];
547         int crlf_in_use = 0;
548         int part_seq = 0;
549         CBufStr *chosen_name;
550
551         // If this is a multipart message, then recursively process it
552         ptr = content_start;
553         part_start = NULL;
554         if (m->is_multipart) {
555
556                 // Tell the client about this message's multipartedness
557                 if (PreMultiPartCallBack != NULL) {
558                         PreMultiPartCallBack("", 
559                                 "", 
560                                 partnum, 
561                                 "",
562                                 NULL, 
563                                 m->b[content_type].Key, 
564                                 m->b[charset].Key,
565                                 0, 
566                                 m->b[encoding].Key, 
567                                 m->b[id].Key, 
568                                 userdata
569                         );
570                 }
571
572                 // Figure out where the boundaries are
573                 m->b[startary].len = snprintf(m->b[startary].Key, SIZ, "--%s", m->b[boundary].Key);
574                 SubMimeHeaders = InitInterestingMimes();
575
576                 while ((*ptr == '\r') || (*ptr == '\n')) {
577                         ptr++;
578                 }
579
580                 if (strncmp(ptr, m->b[startary].Key, m->b[startary].len) == 0) {
581                         ptr += m->b[startary].len;
582                 }
583
584                 while ((*ptr == '\r') || (*ptr == '\n')) {
585                         ptr ++;
586                 }
587
588                 part_start = NULL;
589                 do {
590                         char *optr;
591
592                         optr = ptr;
593                         if (parse_MimeHeaders(SubMimeHeaders, &ptr, content_end) != 0)
594                                 break;
595                         if ((ptr - optr > 2) && (*(ptr - 2) == '\r')) {
596                                 crlf_in_use = 1;
597                         }
598                         
599                         part_start = ptr;
600                         
601                         next_boundary = FindNextContent(ptr, content_end, SubMimeHeaders, m);
602                         if ((next_boundary != NULL) && (next_boundary - part_start < 3)) {
603                                 FlushInterestingMimes(SubMimeHeaders);
604                                 continue;
605                         }
606
607                         if ( (part_start != NULL) && (next_boundary != NULL) ) {
608                                 part_end = next_boundary;
609                                 --part_end;             // omit the trailing LF
610                                 if (crlf_in_use) {
611                                         --part_end;     // omit the trailing CR
612                                 }
613
614                                 if (!IsEmptyStr(partnum)) {
615                                         snprintf(nested_partnum,
616                                                 sizeof nested_partnum,
617                                                 "%s.%d", partnum,
618                                                 ++part_seq);
619                                 }
620                                 else {
621                                         snprintf(nested_partnum,
622                                                 sizeof nested_partnum,
623                                                 "%d", ++part_seq);
624                                 }
625                                 recurseable_mime_parser(nested_partnum,
626                                                         part_start, 
627                                                         part_end,
628                                                         CallBack,
629                                                         PreMultiPartCallBack,
630                                                         PostMultiPartCallBack,
631                                                         userdata,
632                                                         dont_decode, 
633                                                         SubMimeHeaders);
634                         }
635
636                         if (next_boundary != NULL) {
637                                 // If we pass out of scope, don't attempt to read past the end boundary.
638                                 if ((*(next_boundary + m->b[startary].len) == '-') && 
639                                     (*(next_boundary + m->b[startary].len + 1) == '-') ){
640                                         ptr = content_end;
641                                 }
642                                 else {
643                                         // Set up for the next part.
644                                         part_start = strstr(next_boundary, "\n");
645                                         
646                                         // Determine whether newlines are LF or CRLF
647                                         evaluate_crlf_ptr = part_start;
648                                         --evaluate_crlf_ptr;
649                                         if ((*evaluate_crlf_ptr == '\r') && (*(evaluate_crlf_ptr + 1) == '\n')) {
650                                                 crlf_in_use = 1;
651                                         }
652                                         else {
653                                                 crlf_in_use = 0;
654                                         }
655
656                                         // Advance past the LF ... now we're in the next part
657                                         ++part_start;
658                                         ptr = part_start;
659                                 }
660                         }
661                         else {
662                                 // Invalid end of multipart.  Bail out!
663                                 ptr = content_end;
664                         }
665                         FlushInterestingMimes(SubMimeHeaders);
666                 } while ( (ptr < content_end) && (next_boundary != NULL) );
667
668                 free(SubMimeHeaders);
669
670                 if (PostMultiPartCallBack != NULL) {
671                         PostMultiPartCallBack("", 
672                                 "", 
673                                 partnum, 
674                                 "", 
675                                 NULL,
676                                 m->b[content_type].Key, 
677                                 m->b[charset].Key,
678                                 0, 
679                                 m->b[encoding].Key, 
680                                 m->b[id].Key, 
681                                 userdata
682                         );
683                 }
684         }
685
686         // If it's not a multipart message, then do something with it
687         else {
688                 size_t length;
689                 part_start = ptr;
690                 length = content_end - part_start;
691                 ptr = part_end = content_end;
692
693                 // The following code will truncate the MIME part to the size
694                 // specified by the Content-length: header.   We have commented it
695                 // out because these headers have a tendency to be wrong.
696                 //
697                 //      if ( (content_length > 0) && (length > content_length) ) {
698                 //              length = content_length;
699                 //      }
700
701                 // Sometimes the "name" field is tacked on to Content-type,
702                 // and sometimes it's tacked on to Content-disposition.  Use
703                 // whichever one we have.
704                 if (m->b[content_disposition_name].len > m->b[content_type_name].len) {
705                         chosen_name = &m->b[content_disposition_name];
706                 }
707                 else {
708                         chosen_name = &m->b[content_type_name];
709                 }
710         
711                 // Ok, we've got a non-multipart part here, so do something with it.
712                 mime_decode(partnum,
713                                 part_start, 
714                                 length,
715                                 m->b[content_type].Key, 
716                                 m->b[charset].Key,
717                                 m->b[encoding].Key, 
718                                 m->b[disposition].Key, 
719                                 m->b[id].Key, 
720                                 chosen_name->Key, 
721                                 m->b[filename].Key,
722                                 CallBack, 
723                                 NULL,
724                                 NULL,
725                                 userdata, 
726                                 dont_decode
727                         );
728
729                 // Now if it's an encapsulated message/rfc822 then we have to recurse into it
730                 if (!strcasecmp(&m->b[content_type].Key[0], "message/rfc822")) {
731
732                         if (PreMultiPartCallBack != NULL) {
733                                 PreMultiPartCallBack("", 
734                                                          "", 
735                                                          partnum, 
736                                                          "",
737                                                          NULL, 
738                                                          m->b[content_type].Key, 
739                                                          m->b[charset].Key,
740                                                          0, 
741                                                          m->b[encoding].Key, 
742                                                          m->b[id].Key, 
743                                                          userdata);
744                         }
745                         if (CallBack != NULL) {
746                                 if (strlen(partnum) > 0) {
747                                         snprintf(nested_partnum,
748                                                  sizeof nested_partnum,
749                                                  "%s.%d", partnum,
750                                                  ++part_seq);
751                                 }
752                                 else {
753                                         snprintf(nested_partnum,
754                                                  sizeof nested_partnum,
755                                                  "%d", ++part_seq);
756                                 }
757                                 the_mime_parser(nested_partnum,
758                                                 part_start, 
759                                                 part_end,
760                                                 CallBack,
761                                                 PreMultiPartCallBack,
762                                                 PostMultiPartCallBack,
763                                                 userdata,
764                                                 dont_decode
765                                         );
766                         }
767                         if (PostMultiPartCallBack != NULL) {
768                                 PostMultiPartCallBack("", 
769                                         "", 
770                                         partnum, 
771                                         "", 
772                                         NULL,
773                                         m->b[content_type].Key, 
774                                         m->b[charset].Key,
775                                         0, 
776                                         m->b[encoding].Key, 
777                                         m->b[id].Key, 
778                                         userdata
779                                 );
780                         }
781
782
783                 }
784
785         }
786
787 }
788
789
790 // Break out the components of a multipart message
791 // (This function expects to be fed HEADERS + CONTENT)
792 // Note: NULL can be supplied as content_end; in this case, the message is
793 // considered to have ended when the parser encounters a 0x00 byte.
794 void the_mime_parser(char *partnum,
795                      char *content_start, char *content_end,
796                      MimeParserCallBackType CallBack,
797                      MimeParserCallBackType PreMultiPartCallBack,
798                      MimeParserCallBackType PostMultiPartCallBack,
799                      void *userdata,
800                      int dont_decode)
801 {
802         interesting_mime_headers *m;
803
804         // If the caller didn't supply an endpointer, generate one by measure
805         if (content_end == NULL) {
806                 content_end = &content_start[strlen(content_start)];
807         }
808
809         m = InitInterestingMimes();
810         if (!parse_MimeHeaders(m, &content_start, content_end)) {
811                 recurseable_mime_parser(partnum,
812                                         content_start, content_end,
813                                         CallBack,
814                                         PreMultiPartCallBack,
815                                         PostMultiPartCallBack,
816                                         userdata,
817                                         dont_decode,
818                                         m);
819         }
820         free(m);
821 }
822
823
824 // Entry point for the MIME parser.
825 // (This function expects to be fed HEADERS + CONTENT)
826 // Note: NULL can be supplied as content_end; in this case, the message is
827 // considered to have ended when the parser encounters a 0x00 byte.
828 void mime_parser(char *content_start,
829                  char *content_end,
830                  MimeParserCallBackType CallBack,
831                  MimeParserCallBackType PreMultiPartCallBack,
832                  MimeParserCallBackType PostMultiPartCallBack,
833                  void *userdata,
834                  int dont_decode)
835 {
836         the_mime_parser("", content_start, content_end,
837                         CallBack,
838                         PreMultiPartCallBack,
839                         PostMultiPartCallBack,
840                         userdata, dont_decode);
841 }
842
843
844 typedef struct _MimeGuess {
845         const char *Pattern;
846         size_t PatternLen;
847         long PatternOffset;
848         const char *MimeString;
849 } MimeGuess;
850
851 MimeGuess MyMimes [] = {
852         {
853                 "GIF",
854                 3,
855                 0,
856                 "image/gif"
857         },
858         {
859                 "\xff\xd8",
860                 2,
861                 0,
862                 "image/jpeg"
863         },
864         {
865                 "\x89PNG",
866                 4,
867                 0,
868                 "image/png"
869         },
870         { // last...
871                 "",
872                 0,
873                 0,
874                 ""
875         }
876 };
877
878
879 const char *GuessMimeType(const char *data, size_t dlen) {
880         int MimeIndex = 0;
881
882         while (MyMimes[MimeIndex].PatternLen != 0) {
883                 if ((MyMimes[MimeIndex].PatternLen + 
884                      MyMimes[MimeIndex].PatternOffset < dlen) &&
885                     strncmp(MyMimes[MimeIndex].Pattern, 
886                             &data[MyMimes[MimeIndex].PatternOffset], 
887                             MyMimes[MimeIndex].PatternLen) == 0)
888                 {
889                         return MyMimes[MimeIndex].MimeString;
890                 }
891                 MimeIndex ++;
892         }
893         // ok, our simple minded algorythm didn't find anything, 
894         // let the big chegger try it, he wil default to application/octet-stream
895         return (xdg_mime_get_mime_type_for_data(data, dlen));
896 }
897
898
899 const char* GuessMimeByFilename(const char *what, size_t len) {
900         // we know some hardcoded on our own, try them...
901         if ((len > 3) && !strncasecmp(&what[len - 4], ".gif", 4))
902                 return "image/gif";
903         else if ((len > 2) && !strncasecmp(&what[len - 3], ".js", 3))
904                 return  "text/javascript";
905         else if ((len > 3) && !strncasecmp(&what[len - 4], ".txt", 4))
906                 return "text/plain";
907         else if ((len > 3) && !strncasecmp(&what[len - 4], ".css", 4))
908                 return "text/css";
909         else if ((len > 3) && !strncasecmp(&what[len - 4], ".htc", 4))
910                 return "text/x-component";
911         else if ((len > 3) && !strncasecmp(&what[len - 4], ".jpg", 4))
912                 return "image/jpeg";
913         else if ((len > 4) && !strncasecmp(&what[len - 5], ".jpeg", 5))
914                 return "image/jpeg";
915         else if ((len > 3) && !strncasecmp(&what[len - 4], ".png", 4))
916                 return "image/png";
917         else if ((len > 3) && !strncasecmp(&what[len - 4], ".ico", 4))
918                 return "image/x-icon";
919         else if ((len > 3) && !strncasecmp(&what[len - 4], ".vcf", 4))
920                 return "text/x-vcard";
921         else if ((len > 4) && !strncasecmp(&what[len - 5], ".html", 5))
922                 return "text/html";
923         else if ((len > 3) && !strncasecmp(&what[len - 4], ".htm", 4))
924                 return "text/html";
925         else if ((len > 3) && !strncasecmp(&what[len - 4], ".wml", 4))
926                 return "text/vnd.wap.wml";
927         else if ((len > 4) && !strncasecmp(&what[len - 5], ".wmls", 5))
928                 return "text/vnd.wap.wmlscript";
929         else if ((len > 4) && !strncasecmp(&what[len - 5], ".wmlc", 5))
930                 return "application/vnd.wap.wmlc";
931         else if ((len > 5) && !strncasecmp(&what[len - 6], ".wmlsc", 6))
932                 return "application/vnd.wap.wmlscriptc";
933         else if ((len > 4) && !strncasecmp(&what[len - 5], ".wbmp", 5))
934                 return "image/vnd.wap.wbmp";
935         else
936                 // and let xdgmime do the fallback.
937                 return xdg_mime_get_mime_type_from_file_name(what);
938 }
939
940 static HashList *IconHash = NULL;
941
942 typedef struct IconName IconName;
943
944 struct IconName {
945         char *FlatName;
946         char *FileName;
947 };
948
949
950 static void DeleteIcon(void *IconNamePtr) {
951         IconName *Icon = (IconName*) IconNamePtr;
952         free(Icon->FlatName);
953         free(Icon->FileName);
954         free(Icon);
955 }
956
957
958 #define GENSTR "x-generic"
959 #define IGNORE_PREFIX_1 "gnome-mime"
960 int LoadIconDir(const char *DirName) {
961         DIR *filedir = NULL;
962         struct dirent *filedir_entry;
963         int d_namelen;
964         int d_without_ext;
965         IconName *Icon;
966
967         filedir = opendir (DirName);
968         IconHash = NewHash(1, NULL);
969         if (filedir == NULL) {
970                 return 0;
971         }
972
973         while ((filedir_entry = readdir(filedir))) {
974                 char *MinorPtr;
975                 char *PStart;
976 #ifdef _DIRENT_HAVE_D_NAMLEN
977                 d_namelen = filedir_entry->d_namlen;
978 #else
979                 d_namelen = strlen(filedir_entry->d_name);
980 #endif
981                 d_without_ext = d_namelen;
982                 while ((d_without_ext > 0) && (filedir_entry->d_name[d_without_ext] != '.'))
983                         d_without_ext --;
984                 if ((d_without_ext == 0) || (d_namelen < 3))
985                         continue;
986
987                 if ((sizeof(IGNORE_PREFIX_1) < d_namelen) &&
988                     (strncmp(IGNORE_PREFIX_1, 
989                              filedir_entry->d_name, 
990                              sizeof(IGNORE_PREFIX_1) - 1) == 0)) {
991                         PStart = filedir_entry->d_name + sizeof(IGNORE_PREFIX_1);
992                         d_without_ext -= sizeof(IGNORE_PREFIX_1);
993                 }
994                 else {
995                         PStart = filedir_entry->d_name;
996                 }
997                 Icon = malloc(sizeof(IconName));
998
999                 Icon->FileName = malloc(d_namelen + 1);
1000                 memcpy(Icon->FileName, filedir_entry->d_name, d_namelen + 1);
1001
1002                 Icon->FlatName = malloc(d_without_ext + 1);
1003                 memcpy(Icon->FlatName, PStart, d_without_ext);
1004                 Icon->FlatName[d_without_ext] = '\0';
1005                 // Try to find Minor type in image-jpeg
1006                 MinorPtr = strchr(Icon->FlatName, '-');
1007                 if (MinorPtr != NULL) {
1008                         size_t MinorLen;
1009                         MinorLen = 1 + d_without_ext - (MinorPtr - Icon->FlatName + 1);
1010                         if ((MinorLen == sizeof(GENSTR)) && 
1011                             (strncmp(MinorPtr + 1, GENSTR, sizeof(GENSTR)) == 0)) {
1012                                 // ok, we found a generic filename. cut the generic.
1013                                 *MinorPtr = '\0';
1014                                 d_without_ext = d_without_ext - (MinorPtr - Icon->FlatName);
1015                         }
1016                         else { // Map the major / minor separator to /
1017                                 *MinorPtr = '/';
1018                         }
1019                 }
1020
1021                 Put(IconHash, Icon->FlatName, d_without_ext, Icon, DeleteIcon);
1022         }
1023         closedir(filedir);
1024         return 1;
1025 }
1026
1027
1028 const char *GetIconFilename(char *MimeType, size_t len) {
1029         void *vIcon;
1030         IconName *Icon;
1031         
1032         if (IconHash == NULL) {
1033                 return NULL;
1034         }
1035
1036         GetHash(IconHash, MimeType, len, &vIcon), Icon = (IconName*) vIcon;
1037         // didn't find the exact mimetype? try major only.
1038         if (Icon == NULL) {
1039                 char * pMinor;
1040                 pMinor = strchr(MimeType, '/');
1041                 if (pMinor != NULL) {
1042                         *pMinor = '\0';
1043                         GetHash(IconHash, MimeType, pMinor - MimeType, &vIcon),
1044                                 Icon = (IconName*) vIcon;
1045                 }
1046         }
1047         if (Icon == NULL) {
1048                 return NULL;
1049         }
1050
1051         return Icon->FileName;
1052 }
1053
1054
1055 void ShutDownLibCitadelMime(void) {
1056         DeleteHash(&IconHash);
1057 }