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