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;
285 char content_type[SIZ];
286 size_t content_length;
288 char disposition[SIZ];
295 char nested_partnum[SIZ];
297 lprintf(9, "the_mime_parser() called\n");
299 memset(boundary, 0, sizeof boundary);
300 memset(content_type, 0, sizeof content_type);
301 memset(encoding, 0, sizeof encoding);
302 memset(name, 0, sizeof name);
303 memset(filename, 0, sizeof filename);
304 memset(disposition, 0, sizeof disposition);
307 /* If the caller didn't supply an endpointer, generate one by measure */
308 if (content_end == NULL) {
309 content_end = &content_start[strlen(content_start)];
312 /* Learn interesting things from the headers */
315 ptr = memreadline(ptr, buf, sizeof buf);
316 if (ptr >= content_end)
319 for (i = 0; i < strlen(buf); ++i)
322 if (!isspace(buf[0])) {
323 if (!strncasecmp(header, "Content-type: ", 14)) {
324 strcpy(content_type, &header[14]);
325 extract_key(name, content_type, "name");
326 lprintf(9, "Extracted content-type <%s>\n",
329 if (!strncasecmp(header, "Content-Disposition: ", 21)) {
330 strcpy(disposition, &header[21]);
331 extract_key(filename, disposition, "filename");
333 if (!strncasecmp(header, "Content-length: ", 16)) {
334 content_length = (size_t) atol(&header[16]);
336 if (!strncasecmp(header,
337 "Content-transfer-encoding: ", 27))
338 strcpy(encoding, &header[27]);
339 if (strlen(boundary) == 0)
340 extract_key(boundary, header, "boundary");
343 if ((strlen(header) + strlen(buf) + 2) < sizeof(header))
345 } while ((strlen(buf) > 0) && (*ptr != 0));
347 for (i = 0; i < strlen(disposition); ++i)
348 if (disposition[i] == ';')
350 while (isspace(disposition[0]))
351 strcpy(disposition, &disposition[1]);
352 for (i = 0; i < strlen(content_type); ++i)
353 if (content_type[i] == ';')
355 while (isspace(content_type[0]))
356 strcpy(content_type, &content_type[1]);
358 if (strlen(boundary) > 0) {
364 lprintf(9, "is_multipart=%d, boundary=<%s>\n",
365 is_multipart, boundary);
367 /* If this is a multipart message, then recursively process it */
371 /* Tell the client about this message's multipartedness */
372 if (PreMultiPartCallBack != NULL) {
373 PreMultiPartCallBack("", "", partnum, "",
375 0, encoding, userdata);
378 /* Figure out where the boundaries are */
379 sprintf(startary, "--%s", boundary);
380 sprintf(endary, "--%s--", boundary);
382 if ( (!strncasecmp(ptr, startary, strlen(startary)))
383 || (!strncasecmp(ptr, endary, strlen(endary))) ) {
384 lprintf(9, "hit boundary!\n");
385 if (part_start != NULL) {
386 if (strlen(partnum) > 0) {
387 sprintf(nested_partnum, "%s.%d",
388 partnum, ++part_seq);
391 sprintf(nested_partnum, "%d",
394 the_mime_parser(nested_partnum,
395 part_start, part_end,
397 PreMultiPartCallBack,
398 PostMultiPartCallBack,
402 ptr = memreadline(ptr, buf, sizeof(buf));
409 } while ( (strcasecmp(ptr, endary)) && (ptr <= content_end) );
410 if (PostMultiPartCallBack != NULL) {
411 PostMultiPartCallBack("", "", partnum, "", NULL,
412 content_type, 0, encoding, userdata);
417 /* If it's not a multipart message, then do something with it */
419 lprintf(9, "doing non-multipart thing\n");
422 while (ptr < content_end) {
426 part_end = content_end;
428 /* Truncate if the header told us to */
429 if ( (content_length > 0) && (length > content_length) ) {
430 length = content_length;
431 lprintf(9, "truncated to %ld\n", (long)content_length);
436 content_type, encoding, disposition,
438 CallBack, NULL, NULL,
439 userdata, dont_decode);
446 * Entry point for the MIME parser.
447 * (This function expects to be fed HEADERS + CONTENT)
448 * Note: NULL can be supplied as content_end; in this case, the message is
449 * considered to have ended when the parser encounters a 0x00 byte.
451 void mime_parser(char *content_start,
465 void (*PreMultiPartCallBack)
476 void (*PostMultiPartCallBack)
492 lprintf(9, "mime_parser() called\n");
493 the_mime_parser("", content_start, content_end,
495 PreMultiPartCallBack,
496 PostMultiPartCallBack,
497 userdata, dont_decode);