33b249b5062ff3a050dd5532b073e270b550ef11
[citadel.git] / webcit / mime_parser.c
1 /*
2  * $Id$
3  */
4 /**
5  * \defgroup MIME This is the MIME parser for Citadel.
6  *
7  * Copyright (c) 1998-2005 by Art Cancro
8  * This code is distributed under the terms of the GNU General Public License.
9  * \ingroup WebcitHttpServer
10  */
11 /*@{*/
12 #include "webcit.h"
13 #include "webserver.h"
14 #include "mime_parser.h"
15
16
17 void extract_key(char *target, char *source, char *key)
18 {
19         int a, b;
20
21         strcpy(target, source);
22         for (a = 0; a < strlen(target); ++a) {
23                 if ((!strncasecmp(&target[a], key, strlen(key)))
24                     && (target[a + strlen(key)] == '=')) {
25                         strcpy(target, &target[a + strlen(key) + 1]);
26                         if (target[0] == 34)
27                                 strcpy(target, &target[1]);
28                         for (b = 0; b < strlen(target); ++b)
29                                 if (target[b] == 34)
30                                         target[b] = 0;
31                         return;
32                 }
33         }
34         strcpy(target, "");
35 }
36
37
38 /*
39  * For non-multipart messages, we need to generate a quickie partnum of "1"
40  * to return to callback functions.  Some callbacks demand it.
41  */
42 char *fixed_partnum(char *supplied_partnum) {
43         if (supplied_partnum == NULL) return "1";
44         if (strlen(supplied_partnum)==0) return "1";
45         return supplied_partnum;
46 }
47
48
49
50 /*
51  * Convert "quoted-printable" to binary.  Returns number of bytes decoded.
52  */
53 int CtdlDecodeQuotedPrintable(char *decoded, char *encoded, int sourcelen) {
54         char buf[SIZ];
55         int buf_length = 0;
56         int soft_line_break = 0;
57         unsigned int ch;
58         int decoded_length = 0;
59         int i;
60
61         decoded[0] = 0;
62         decoded_length = 0;
63         memset(buf, '\0', SIZ);
64         buf_length = 0;
65
66         for (i = 0; i < sourcelen; ++i) {
67                 size_t buflen = strlen(buf);
68                 buf[buf_length++] = encoded[i];
69
70                 if ( (encoded[i] == '\n')
71                    || (encoded[i] == 0)
72                    || (i == (sourcelen-1)) ) {
73                         buf[buf_length++] = 0;
74
75                         /*** begin -- process one line ***/
76
77                         if (buf[buflen-1] == '\n') {
78                                 buf[buflen-1] = 0;
79                                 buflen --;
80                         }
81                         if (buf[buflen-1] == '\r') {
82                                 buf[buflen-1] = 0;
83                                 buflen --;
84                         }
85                         while (isspace(buf[buflen-1])) {
86                                 buf[buflen-1] = 0;
87                                 buflen --;
88                         }
89                         soft_line_break = 0;
90
91                         while (buflen > 0) {
92                                 if ((buf[0] ==  '=') && (buf[1] == '\0')) {
93                                         soft_line_break = 1;
94                                         buf[0] = '\0';
95                                         buflen = 0;
96                                 } else if ((buflen>=3) && (buf[0]=='=')) {
97                                         sscanf(&buf[1], "%02x", &ch);
98                                         decoded[decoded_length++] = ch;
99                                         strcpy(buf, &buf[3]);
100                                         buflen -=3;
101                                 } else {
102                                         decoded[decoded_length++] = buf[0];
103                                         strcpy(buf, &buf[1]);
104                                         buflen --;
105                                 }
106                         }
107                         if (soft_line_break == 0) {
108                                 decoded[decoded_length++] = '\r';
109                                 decoded[decoded_length++] = '\n';
110                         }
111                         buf_length = 0;
112                         /*** end -- process one line ***/
113                 }
114         }
115
116         decoded[decoded_length++] = 0;
117         return(decoded_length);
118 }
119
120 /*
121  * Given a message or message-part body and a length, handle any necessary
122  * decoding and pass the request up the stack.
123  */
124 void mime_decode(char *partnum,
125                  char *part_start, size_t length,
126                  char *content_type, char *charset, char *encoding,
127                  char *disposition,
128                  char *name, char *filename,
129                  void (*CallBack)
130                   (char *cbname,
131                    char *cbfilename,
132                    char *cbpartnum,
133                    char *cbdisp,
134                    void *cbcontent,
135                    char *cbtype,
136                    char *cbcharset,
137                    size_t cblength,
138                    char *cbencoding,
139                    void *cbuserdata),
140                  void (*PreMultiPartCallBack)
141                   (char *cbname,
142                    char *cbfilename,
143                    char *cbpartnum,
144                    char *cbdisp,
145                    void *cbcontent,
146                    char *cbtype,
147                    char *cbcharset,
148                    size_t cblength,
149                    char *cbencoding,
150                    void *cbuserdata),
151                  void (*PostMultiPartCallBack)
152                   (char *cbname,
153                    char *cbfilename,
154                    char *cbpartnum,
155                    char *cbdisp,
156                    void *cbcontent,
157                    char *cbtype,
158                    char *cbcharset,
159                    size_t cblength,
160                    char *cbencoding,
161                    void *cbuserdata),
162                   void *userdata,
163                   int dont_decode
164 )
165 {
166
167         char *decoded;
168         size_t bytes_decoded = 0;
169
170         /* Some encodings aren't really encodings */
171         if (!strcasecmp(encoding, "7bit"))
172                 strcpy(encoding, "");
173         if (!strcasecmp(encoding, "8bit"))
174                 strcpy(encoding, "");
175         if (!strcasecmp(encoding, "binary"))
176                 strcpy(encoding, "");
177
178         /* If this part is not encoded, send as-is */
179         if ( (strlen(encoding) == 0) || (dont_decode)) {
180                 if (CallBack != NULL) {
181                         CallBack(name, filename, fixed_partnum(partnum),
182                                 disposition, part_start,
183                                 content_type, charset, length, encoding, userdata);
184                         }
185                 return;
186         }
187         
188         /* Fail silently if we hit an unknown encoding. */
189         if ((strcasecmp(encoding, "base64"))
190             && (strcasecmp(encoding, "quoted-printable"))) {
191                 return;
192         }
193
194         /*
195          * Allocate a buffer for the decoded data.  The output buffer is slightly
196          * larger than the input buffer; this assumes that the decoded data
197          * will never be significantly larger than the encoded data.  This is a
198          * safe assumption with base64, uuencode, and quoted-printable.
199          */
200         decoded = malloc(length + 32768);
201         if (decoded == NULL) {
202                 return;
203         }
204
205         if (!strcasecmp(encoding, "base64")) {
206                 bytes_decoded = CtdlDecodeBase64(decoded, part_start, length);
207         }
208         else if (!strcasecmp(encoding, "quoted-printable")) {
209                 bytes_decoded = CtdlDecodeQuotedPrintable(decoded, part_start, length);
210         }
211
212         if (bytes_decoded > 0) if (CallBack != NULL) {
213                 CallBack(name, filename, fixed_partnum(partnum),
214                         disposition, decoded,
215                         content_type, charset, bytes_decoded, "binary", userdata);
216         }
217
218         free(decoded);
219 }
220
221 /*
222  * Break out the components of a multipart message
223  * (This function expects to be fed HEADERS + CONTENT)
224  * Note: NULL can be supplied as content_end; in this case, the message is
225  * considered to have ended when the parser encounters a 0x00 byte.
226  */
227 void the_mime_parser(char *partnum,
228                      char *content_start, char *content_end,
229                      void (*CallBack)
230                       (char *cbname,
231                        char *cbfilename,
232                        char *cbpartnum,
233                        char *cbdisp,
234                        void *cbcontent,
235                        char *cbtype,
236                        char *cbcharset,
237                        size_t cblength,
238                        char *cbencoding,
239                        void *cbuserdata),
240                      void (*PreMultiPartCallBack)
241                       (char *cbname,
242                        char *cbfilename,
243                        char *cbpartnum,
244                        char *cbdisp,
245                        void *cbcontent,
246                        char *cbtype,
247                        char *cbcharset,
248                        size_t cblength,
249                        char *cbencoding,
250                        void *cbuserdata),
251                      void (*PostMultiPartCallBack)
252                       (char *cbname,
253                        char *cbfilename,
254                        char *cbpartnum,
255                        char *cbdisp,
256                        void *cbcontent,
257                        char *cbtype,
258                        char *cbcharset,
259                        size_t cblength,
260                        char *cbencoding,
261                        void *cbuserdata),
262                       void *userdata,
263                       int dont_decode
264 )
265 {
266
267         char *ptr;
268         char *srch = NULL;
269         char *part_start, *part_end = NULL;
270         char buf[SIZ];
271         char *header;
272         char *boundary;
273         char *startary;
274         size_t startary_len = 0;
275         char *endary;
276         char *next_boundary;
277         char *content_type;
278         char *charset;
279         size_t content_length;
280         char *encoding;
281         char *disposition;
282         char *name = NULL;
283         char *content_type_name;
284         char *content_disposition_name;
285         char *filename;
286         int is_multipart;
287         int part_seq = 0;
288         int i;
289         size_t length;
290         char nested_partnum[SIZ];
291
292         ptr = content_start;
293         content_length = 0;
294
295         boundary = malloc(SIZ);
296         memset(boundary, 0, SIZ);
297
298         startary = malloc(SIZ);
299         memset(startary, 0, SIZ);
300
301         endary = malloc(SIZ);
302         memset(endary, 0, SIZ);
303
304         header = malloc(SIZ);
305         memset(header, 0, SIZ);
306
307         content_type = malloc(SIZ);
308         memset(content_type, 0, SIZ);
309
310         charset = malloc(SIZ);
311         memset(charset, 0, SIZ);
312
313         encoding = malloc(SIZ);
314         memset(encoding, 0, SIZ);
315
316         content_type_name = malloc(SIZ);
317         memset(content_type_name, 0, SIZ);
318
319         content_disposition_name = malloc(SIZ);
320         memset(content_disposition_name, 0, SIZ);
321
322         filename = malloc(SIZ);
323         memset(filename, 0, SIZ);
324
325         disposition = malloc(SIZ);
326         memset(disposition, 0, SIZ);
327
328         /* If the caller didn't supply an endpointer, generate one by measure */
329         if (content_end == NULL) {
330                 content_end = &content_start[strlen(content_start)];
331         }
332
333         /* Learn interesting things from the headers */
334         strcpy(header, "");
335         do {
336                 ptr = memreadline(ptr, buf, SIZ);
337                 if (ptr >= content_end) {
338                         goto end_parser;
339                 }
340
341                 for (i = 0; i < strlen(buf); ++i) {
342                         if (isspace(buf[i])) {
343                                 buf[i] = ' ';
344                         }
345                 }
346
347                 if (!isspace(buf[0])) {
348                         if (!strncasecmp(header, "Content-type: ", 14)) {
349                                 strcpy(content_type, &header[14]);
350                                 extract_key(content_type_name, content_type, "name");
351                                 extract_key(charset, content_type, "charset");
352                                 /* Deal with weird headers */
353                                 if (strchr(content_type, ' '))
354                                         *(strchr(content_type, ' ')) = '\0';
355                                 if (strchr(content_type, ';'))
356                                         *(strchr(content_type, ';')) = '\0';
357                         }
358                         if (!strncasecmp(header, "Content-Disposition: ", 21)) {
359                                 strcpy(disposition, &header[21]);
360                                 extract_key(content_disposition_name, disposition, "name");
361                                 extract_key(filename, disposition, "filename");
362                         }
363                         if (!strncasecmp(header, "Content-length: ", 16)) {
364                                 content_length = (size_t) atol(&header[16]);
365                         }
366                         if (!strncasecmp(header,
367                                       "Content-transfer-encoding: ", 27))
368                                 strcpy(encoding, &header[27]);
369                         if (strlen(boundary) == 0)
370                                 extract_key(boundary, header, "boundary");
371                         strcpy(header, "");
372                 }
373                 if ((strlen(header) + strlen(buf) + 2) < SIZ)
374                         strcat(header, buf);
375         } while ((strlen(buf) > 0) && (*ptr != 0));
376
377         if (strchr(disposition, ';'))
378                 *(strchr(disposition, ';')) = '\0';
379         striplt(disposition);
380         if (strchr(content_type, ';'))
381                 *(strchr(content_type, ';')) = '\0';
382         striplt(content_type);
383
384         if (strlen(boundary) > 0) {
385                 is_multipart = 1;
386         } else {
387                 is_multipart = 0;
388         }
389
390         /* If this is a multipart message, then recursively process it */
391         part_start = NULL;
392         if (is_multipart) {
393
394                 /* Tell the client about this message's multipartedness */
395                 if (PreMultiPartCallBack != NULL) {
396                         PreMultiPartCallBack("", "", partnum, "",
397                                 NULL, content_type, charset,
398                                 0, encoding, userdata);
399                 }
400
401                 /* Figure out where the boundaries are */
402                 snprintf(startary, SIZ, "--%s", boundary);
403                 snprintf(endary, SIZ, "--%s--", boundary);
404                 startary_len = strlen(startary);
405
406                 part_start = NULL;
407                 do {
408                         next_boundary = NULL;
409                         for (srch=ptr; srch<content_end; ++srch) {
410                                 if (!memcmp(srch, startary, startary_len)) {
411                                         next_boundary = srch;
412                                         srch = content_end;
413                                 }
414                         }
415
416                         if ( (part_start != NULL) && (next_boundary != NULL) ) {
417                                 part_end = next_boundary;
418                                 --part_end;
419
420                                 if (strlen(partnum) > 0) {
421                                         snprintf(nested_partnum,
422                                                  sizeof nested_partnum,
423                                                  "%s.%d", partnum,
424                                                  ++part_seq);
425                                 }
426                                 else {
427                                         snprintf(nested_partnum,
428                                                  sizeof nested_partnum,
429                                                  "%d", ++part_seq);
430                                 }
431                                 the_mime_parser(nested_partnum,
432                                             part_start, part_end,
433                                                 CallBack,
434                                                 PreMultiPartCallBack,
435                                                 PostMultiPartCallBack,
436                                                 userdata,
437                                                 dont_decode);
438                         }
439
440                         if (next_boundary != NULL) {
441                                 /* If we pass out of scope, don't attempt to
442                                  * read past the end boundary. */
443                                 if (!strcmp(next_boundary, endary)) {
444                                         ptr = content_end;
445                                 }
446                                 else {
447                                         /* Set up for the next part. */
448                                         part_start = strstr(next_boundary, "\n");
449                                         ++part_start;
450                                         ptr = part_start;
451                                 }
452                         }
453                         else {
454                                 /* Invalid end of multipart.  Bail out! */
455                                 ptr = content_end;
456                         }
457                 } while ( (ptr < content_end) && (next_boundary != NULL) );
458
459                 if (PostMultiPartCallBack != NULL) {
460                         PostMultiPartCallBack("", "", partnum, "", NULL,
461                                 content_type, charset, 0, encoding, userdata);
462                 }
463                 goto end_parser;
464         }
465
466         /* If it's not a multipart message, then do something with it */
467         if (!is_multipart) {
468                 part_start = ptr;
469                 length = 0;
470                 while (ptr < content_end) {
471                         ++ptr;
472                         ++length;
473                 }
474                 part_end = content_end;
475                 /* fix an off-by-one error */
476                 --part_end;
477                 --length;
478                 
479                 /* Truncate if the header told us to */
480                 if ( (content_length > 0) && (length > content_length) ) {
481                         length = content_length;
482                 }
483
484                 /* Sometimes the "name" field is tacked on to Content-type,
485                  * and sometimes it's tacked on to Content-disposition.  Use
486                  * whichever one we have.
487                  */
488                 if (strlen(content_disposition_name) > strlen(content_type_name)) {
489                         name = content_disposition_name;
490                 }
491                 else {
492                         name = content_type_name;
493                 }
494         
495                 /* lprintf(CTDL_DEBUG, "mime_decode part=%s, len=%d, type=%s, charset=%s, encoding=%s\n",
496                         partnum, length, content_type, charset, encoding); */
497
498                 /* Ok, we've got a non-multipart part here, so do something with it.
499                  */
500                 mime_decode(partnum,
501                         part_start, length,
502                         content_type, charset, encoding, disposition,
503                         name, filename,
504                         CallBack, NULL, NULL,
505                         userdata, dont_decode
506                 );
507
508                 /*
509                  * Now if it's an encapsulated message/rfc822 then we have to recurse into it
510                  */
511                 if (!strcasecmp(content_type, "message/rfc822")) {
512
513                         if (PreMultiPartCallBack != NULL) {
514                                 PreMultiPartCallBack("", "", partnum, "",
515                                         NULL, content_type, charset,
516                                         0, encoding, userdata);
517                         }
518                         if (CallBack != NULL) {
519                                 if (strlen(partnum) > 0) {
520                                         snprintf(nested_partnum,
521                                                  sizeof nested_partnum,
522                                                  "%s.%d", partnum,
523                                                  ++part_seq);
524                                 }
525                                 else {
526                                         snprintf(nested_partnum,
527                                                  sizeof nested_partnum,
528                                                  "%d", ++part_seq);
529                                 }
530                                 the_mime_parser(nested_partnum,
531                                         part_start, part_end,
532                                         CallBack,
533                                         PreMultiPartCallBack,
534                                         PostMultiPartCallBack,
535                                         userdata,
536                                         dont_decode
537                                 );
538                         }
539                         if (PostMultiPartCallBack != NULL) {
540                                 PostMultiPartCallBack("", "", partnum, "", NULL,
541                                         content_type, charset, 0, encoding, userdata);
542                         }
543
544
545                 }
546
547         }
548
549 end_parser:     /* free the buffers!  end the oppression!! */
550         free(boundary);
551         free(startary);
552         free(endary);   
553         free(header);
554         free(content_type);
555         free(charset);
556         free(encoding);
557         free(content_type_name);
558         free(content_disposition_name);
559         free(filename);
560         free(disposition);
561 }
562
563
564
565 /*
566  * Entry point for the MIME parser.
567  * (This function expects to be fed HEADERS + CONTENT)
568  * Note: NULL can be supplied as content_end; in this case, the message is
569  * considered to have ended when the parser encounters a 0x00 byte.
570  */
571 void mime_parser(char *content_start,
572                 char *content_end,
573
574                  void (*CallBack)
575                   (char *cbname,
576                    char *cbfilename,
577                    char *cbpartnum,
578                    char *cbdisp,
579                    void *cbcontent,
580                    char *cbtype,
581                    char *cbcharset,
582                    size_t cblength,
583                    char *cbencoding,
584                    void *cbuserdata),
585
586                  void (*PreMultiPartCallBack)
587                   (char *cbname,
588                    char *cbfilename,
589                    char *cbpartnum,
590                    char *cbdisp,
591                    void *cbcontent,
592                    char *cbtype,
593                    char *cbcharset,
594                    size_t cblength,
595                    char *cbencoding,
596                    void *cbuserdata),
597
598                  void (*PostMultiPartCallBack)
599                   (char *cbname,
600                    char *cbfilename,
601                    char *cbpartnum,
602                    char *cbdisp,
603                    void *cbcontent,
604                    char *cbtype,
605                    char *cbcharset,
606                    size_t cblength,
607                    char *cbencoding,
608                    void *cbuserdata),
609
610                   void *userdata,
611                   int dont_decode
612 )
613 {
614
615         the_mime_parser("", content_start, content_end,
616                         CallBack,
617                         PreMultiPartCallBack,
618                         PostMultiPartCallBack,
619                         userdata, dont_decode);
620 }
621
622
623 /*@}*/