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