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