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