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