4 * This is the MIME parser for Citadel. Sometimes it actually works.
6 * Copyright (c) 1998-2001 by Art Cancro
7 * This code is distributed under the terms of the GNU General Public License.
19 #include <sys/types.h>
26 #include "dynloader.h"
27 #include "sysdep_decls.h"
28 #include "mime_parser.h"
32 void extract_key(char *target, char *source, char *key)
36 strcpy(target, source);
37 for (a = 0; a < strlen(target); ++a) {
38 if ((!strncasecmp(&target[a], key, strlen(key)))
39 && (target[a + strlen(key)] == '=')) {
40 strcpy(target, &target[a + strlen(key) + 1]);
42 strcpy(target, &target[1]);
43 for (b = 0; b < strlen(target); ++b)
54 * For non-multipart messages, we need to generate a quickie partnum of "1"
55 * to return to callback functions. Some callbacks demand it.
57 char *fixed_partnum(char *supplied_partnum) {
58 if (supplied_partnum == NULL) return "1";
59 if (strlen(supplied_partnum)==0) return "1";
60 return supplied_partnum;
66 * Convert "quoted-printable" to binary. Returns number of bytes decoded.
68 int decode_quoted_printable(char *decoded, char *encoded, int sourcelen) {
71 int soft_line_break = 0;
73 int decoded_length = 0;
81 for (i = 0; i < sourcelen; ++i) {
83 buf[buf_length++] = encoded[i];
85 if ( (encoded[i] == '\n')
87 || (i == (sourcelen-1)) ) {
88 buf[buf_length++] = 0;
90 /*** begin -- process one line ***/
92 if (buf[strlen(buf)-1] == '\n') {
93 buf[strlen(buf)-1] = 0;
95 if (buf[strlen(buf)-1] == '\r') {
96 buf[strlen(buf)-1] = 0;
98 while (isspace(buf[strlen(buf)-1])) {
99 buf[strlen(buf)-1] = 0;
103 while (strlen(buf) > 0) {
104 if (!strcmp(buf, "=")) {
107 } else if ((strlen(buf)>=3) && (buf[0]=='=')) {
108 sscanf(&buf[1], "%02x", &ch);
109 decoded[decoded_length++] = ch;
110 strcpy(buf, &buf[3]);
112 decoded[decoded_length++] = buf[0];
113 strcpy(buf, &buf[1]);
116 if (soft_line_break == 0) {
117 decoded[decoded_length++] = '\r';
118 decoded[decoded_length++] = '\n';
121 /*** end -- process one line ***/
125 decoded[decoded_length++] = 0;
126 return(decoded_length);
130 * Given a message or message-part body and a length, handle any necessary
131 * decoding and pass the request up the stack.
133 void mime_decode(char *partnum,
134 char *part_start, size_t length,
135 char *content_type, char *encoding,
137 char *name, char *filename,
148 void (*PreMultiPartCallBack)
158 void (*PostMultiPartCallBack)
174 size_t bytes_decoded = 0;
176 lprintf(9, "mime_decode() called\n");
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, "");
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);
196 if ((strcasecmp(encoding, "base64"))
197 && (strcasecmp(encoding, "quoted-printable"))) {
198 lprintf(9, "ERROR: unknown MIME encoding '%s'\n", encoding);
202 * Allocate a buffer for the decoded data. The output buffer is the
203 * same size as the input buffer; this assumes that the decoded data
204 * will never be larger than the encoded data. This is a safe
205 * assumption with base64, uuencode, and quoted-printable.
207 lprintf(9, "About to allocate %d bytes for decoded part\n",
209 decoded = mallok(length+2048);
210 if (decoded == NULL) {
211 lprintf(9, "ERROR: cannot allocate memory.\n");
214 lprintf(9, "Got it!\n");
216 lprintf(9, "Decoding %s\n", encoding);
217 if (!strcasecmp(encoding, "base64")) {
218 bytes_decoded = decode_base64(decoded, part_start, length);
220 else if (!strcasecmp(encoding, "quoted-printable")) {
221 bytes_decoded = decode_quoted_printable(decoded,
224 lprintf(9, "Bytes decoded: %d\n", bytes_decoded);
226 if (bytes_decoded > 0) if (CallBack != NULL) {
227 CallBack(name, filename, fixed_partnum(partnum),
228 disposition, decoded,
229 content_type, bytes_decoded, "binary", userdata);
236 * Break out the components of a multipart message
237 * (This function expects to be fed HEADERS + CONTENT)
238 * Note: NULL can be supplied as content_end; in this case, the message is
239 * considered to have ended when the parser encounters a 0x00 byte.
241 void the_mime_parser(char *partnum,
242 char *content_start, char *content_end,
253 void (*PreMultiPartCallBack)
263 void (*PostMultiPartCallBack)
279 char *part_start, *part_end = NULL;
286 size_t content_length;
295 char nested_partnum[SIZ];
297 lprintf(9, "the_mime_parser() called\n");
301 boundary = mallok(SIZ);
302 memset(boundary, 0, SIZ);
304 startary = mallok(SIZ);
305 memset(startary, 0, SIZ);
307 endary = mallok(SIZ);
308 memset(endary, 0, SIZ);
310 header = mallok(SIZ);
311 memset(header, 0, SIZ);
313 content_type = mallok(SIZ);
314 memset(content_type, 0, SIZ);
316 encoding = mallok(SIZ);
317 memset(encoding, 0, SIZ);
320 memset(name, 0, SIZ);
322 filename = mallok(SIZ);
323 memset(filename, 0, SIZ);
325 disposition = mallok(SIZ);
326 memset(disposition, 0, SIZ);
328 /* If the caller didn't supply an endpointer, generate one by measure */
329 if (content_end == NULL) {
330 content_end = &content_start[strlen(content_start)];
333 /* Learn interesting things from the headers */
336 ptr = memreadline(ptr, buf, SIZ);
337 if (ptr >= content_end) {
341 for (i = 0; i < strlen(buf); ++i)
344 if (!isspace(buf[0])) {
345 if (!strncasecmp(header, "Content-type: ", 14)) {
346 strcpy(content_type, &header[14]);
347 extract_key(name, content_type, "name");
348 lprintf(9, "Extracted content-type <%s>\n",
351 if (!strncasecmp(header, "Content-Disposition: ", 21)) {
352 strcpy(disposition, &header[21]);
353 extract_key(filename, disposition, "filename");
355 if (!strncasecmp(header, "Content-length: ", 16)) {
356 content_length = (size_t) atol(&header[16]);
358 if (!strncasecmp(header,
359 "Content-transfer-encoding: ", 27))
360 strcpy(encoding, &header[27]);
361 if (strlen(boundary) == 0)
362 extract_key(boundary, header, "boundary");
365 if ((strlen(header) + strlen(buf) + 2) < SIZ)
367 } while ((strlen(buf) > 0) && (*ptr != 0));
369 for (i = 0; i < strlen(disposition); ++i)
370 if (disposition[i] == ';')
372 while (isspace(disposition[0]))
373 strcpy(disposition, &disposition[1]);
374 for (i = 0; i < strlen(content_type); ++i)
375 if (content_type[i] == ';')
377 while (isspace(content_type[0]))
378 strcpy(content_type, &content_type[1]);
380 if (strlen(boundary) > 0) {
386 lprintf(9, "is_multipart=%d, boundary=<%s>\n",
387 is_multipart, boundary);
389 /* If this is a multipart message, then recursively process it */
393 /* Tell the client about this message's multipartedness */
394 if (PreMultiPartCallBack != NULL) {
395 PreMultiPartCallBack("", "", partnum, "",
397 0, encoding, userdata);
400 /* Figure out where the boundaries are */
401 snprintf(startary, SIZ, "--%s", boundary);
402 snprintf(endary, SIZ, "--%s--", boundary);
404 if ( (!strncasecmp(ptr, startary, strlen(startary)))
405 || (!strncasecmp(ptr, endary, strlen(endary))) ) {
406 lprintf(9, "hit boundary!\n");
407 if (part_start != NULL) {
408 if (strlen(partnum) > 0) {
409 snprintf(nested_partnum,
410 sizeof nested_partnum,
415 snprintf(nested_partnum,
416 sizeof nested_partnum,
419 the_mime_parser(nested_partnum,
420 part_start, part_end,
422 PreMultiPartCallBack,
423 PostMultiPartCallBack,
427 ptr = memreadline(ptr, buf, SIZ);
434 } while ( (strcasecmp(ptr, endary)) && (ptr <= content_end) );
435 if (PostMultiPartCallBack != NULL) {
436 PostMultiPartCallBack("", "", partnum, "", NULL,
437 content_type, 0, encoding, userdata);
442 /* If it's not a multipart message, then do something with it */
444 lprintf(9, "doing non-multipart thing\n");
447 while (ptr < content_end) {
451 part_end = content_end;
453 /* Truncate if the header told us to */
454 if ( (content_length > 0) && (length > content_length) ) {
455 length = content_length;
456 lprintf(9, "truncated to %ld\n", (long)content_length);
461 content_type, encoding, disposition,
463 CallBack, NULL, NULL,
464 userdata, dont_decode);
467 end_parser: /* free the buffers! end the oppression!! */
482 * Entry point for the MIME parser.
483 * (This function expects to be fed HEADERS + CONTENT)
484 * Note: NULL can be supplied as content_end; in this case, the message is
485 * considered to have ended when the parser encounters a 0x00 byte.
487 void mime_parser(char *content_start,
501 void (*PreMultiPartCallBack)
512 void (*PostMultiPartCallBack)
528 lprintf(9, "mime_parser() called\n");
529 the_mime_parser("", content_start, content_end,
531 PreMultiPartCallBack,
532 PostMultiPartCallBack,
533 userdata, dont_decode);