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