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