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 "mime_parser.h"
29 void extract_key(char *target, char *source, char *key)
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]);
39 strcpy(target, &target[1]);
40 for (b = 0; b < strlen(target); ++b)
51 * For non-multipart messages, we need to generate a quickie partnum of "1"
52 * to return to callback functions. Some callbacks demand it.
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;
63 * Convert "quoted-printable" to binary. Returns number of bytes decoded.
65 int decode_quoted_printable(char *decoded, char *encoded, int sourcelen) {
68 int soft_line_break = 0;
70 int decoded_length = 0;
78 for (i = 0; i < sourcelen; ++i) {
80 buf[buf_length++] = encoded[i];
82 if ( (encoded[i] == '\n')
84 || (i == (sourcelen-1)) ) {
85 buf[buf_length++] = 0;
87 /*** begin -- process one line ***/
89 if (buf[strlen(buf)-1] == '\n') {
90 buf[strlen(buf)-1] = 0;
92 if (buf[strlen(buf)-1] == '\r') {
93 buf[strlen(buf)-1] = 0;
95 while (isspace(buf[strlen(buf)-1])) {
96 buf[strlen(buf)-1] = 0;
100 while (strlen(buf) > 0) {
101 if (!strcmp(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]);
109 decoded[decoded_length++] = buf[0];
110 strcpy(buf, &buf[1]);
113 if (soft_line_break == 0) {
114 decoded[decoded_length++] = '\r';
115 decoded[decoded_length++] = '\n';
118 /*** end -- process one line ***/
122 decoded[decoded_length++] = 0;
123 return(decoded_length);
127 * Given a message or message-part body and a length, handle any necessary
128 * decoding and pass the request up the stack.
130 void mime_decode(char *partnum,
131 char *part_start, size_t length,
132 char *content_type, char *encoding,
134 char *name, char *filename,
145 void (*PreMultiPartCallBack)
155 void (*PostMultiPartCallBack)
171 size_t bytes_decoded = 0;
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, "");
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);
191 if ((strcasecmp(encoding, "base64"))
192 && (strcasecmp(encoding, "quoted-printable"))) {
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.
201 decoded = mallok(length+2048);
202 if (decoded == NULL) {
206 if (!strcasecmp(encoding, "base64")) {
207 bytes_decoded = CtdlDecodeBase64(decoded, part_start, length);
209 else if (!strcasecmp(encoding, "quoted-printable")) {
210 bytes_decoded = decode_quoted_printable(decoded,
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);
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.
229 void the_mime_parser(char *partnum,
230 char *content_start, char *content_end,
241 void (*PreMultiPartCallBack)
251 void (*PostMultiPartCallBack)
267 char *part_start, *part_end = NULL;
274 size_t content_length;
283 char nested_partnum[SIZ];
288 boundary = mallok(SIZ);
289 memset(boundary, 0, SIZ);
291 startary = mallok(SIZ);
292 memset(startary, 0, SIZ);
294 endary = mallok(SIZ);
295 memset(endary, 0, SIZ);
297 header = mallok(SIZ);
298 memset(header, 0, SIZ);
300 content_type = mallok(SIZ);
301 memset(content_type, 0, SIZ);
303 encoding = mallok(SIZ);
304 memset(encoding, 0, SIZ);
307 memset(name, 0, SIZ);
309 filename = mallok(SIZ);
310 memset(filename, 0, SIZ);
312 disposition = mallok(SIZ);
313 memset(disposition, 0, SIZ);
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)];
320 /* Learn interesting things from the headers */
323 ptr = memreadline(ptr, buf, SIZ);
324 if (ptr >= content_end) {
328 for (i = 0; i < strlen(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';
341 if (!strncasecmp(header, "Content-Disposition: ", 21)) {
342 strcpy(disposition, &header[21]);
343 extract_key(filename, disposition, "filename");
345 if (!strncasecmp(header, "Content-length: ", 16)) {
346 content_length = (size_t) atol(&header[16]);
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");
355 if ((strlen(header) + strlen(buf) + 2) < SIZ)
357 } while ((strlen(buf) > 0) && (*ptr != 0));
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);
366 if (strlen(boundary) > 0) {
372 /* If this is a multipart message, then recursively process it */
376 /* Tell the client about this message's multipartedness */
377 if (PreMultiPartCallBack != NULL) {
378 PreMultiPartCallBack("", "", partnum, "",
380 0, encoding, userdata);
383 /* Figure out where the boundaries are */
384 snprintf(startary, SIZ, "--%s", boundary);
385 snprintf(endary, SIZ, "--%s--", boundary);
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,
397 snprintf(nested_partnum,
398 sizeof nested_partnum,
401 the_mime_parser(nested_partnum,
402 part_start, part_end,
404 PreMultiPartCallBack,
405 PostMultiPartCallBack,
409 ptr = memreadline(ptr, buf, SIZ);
416 } while ( (strcasecmp(ptr, endary)) && (ptr <= content_end) );
417 if (PostMultiPartCallBack != NULL) {
418 PostMultiPartCallBack("", "", partnum, "", NULL,
419 content_type, 0, encoding, userdata);
424 /* If it's not a multipart message, then do something with it */
428 while (ptr < content_end) {
432 part_end = content_end;
434 /* Truncate if the header told us to */
435 if ( (content_length > 0) && (length > content_length) ) {
436 length = content_length;
441 content_type, encoding, disposition,
443 CallBack, NULL, NULL,
444 userdata, dont_decode);
447 end_parser: /* free the buffers! end the oppression!! */
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.
467 void mime_parser(char *content_start,
481 void (*PreMultiPartCallBack)
492 void (*PostMultiPartCallBack)
508 the_mime_parser("", content_start, content_end,
510 PreMultiPartCallBack,
511 PostMultiPartCallBack,
512 userdata, dont_decode);