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