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