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