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