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