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