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