1 /*
2 * Decode MIME parts.
3 */
4 /* (C) Copyright 1993,1994 by Carnegie Mellon University
5 * All Rights Reserved.
6 *
7 * Permission to use, copy, modify, distribute, and sell this software
8 * and its documentation for any purpose is hereby granted without
9 * fee, provided that the above copyright notice appear in all copies
10 * and that both that copyright notice and this permission notice
11 * appear in supporting documentation, and that the name of Carnegie
12 * Mellon University not be used in advertising or publicity
13 * pertaining to distribution of the software without specific,
14 * written prior permission. Carnegie Mellon University makes no
15 * representations about the suitability of this software for any
16 * purpose. It is provided "as is" without express or implied
17 * warranty.
18 *
19 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
20 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
21 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
22 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
23 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
24 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
25 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
26 * SOFTWARE. */
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <ctype.h>
32 #include <unistd.h>
33 #include "xmalloc.h"
34 #include "common.h"
35 #include "part.h"
36 #include "md5.h"
37
38 extern char *os_idtodir(char *id);
39 extern FILE *os_newtypedfile(char *fname, char *contentType, int flags, params contentParams);
40 extern FILE *os_createfile(char *fname);
41 extern FILE *os_createnewfile(char *fname);
42 extern char *md5contextTo64(MD5_CTX *context);
43 extern void warn(char *s);
44 extern void os_perror(char *str);
45 extern void chat(char *s);
46 extern void os_donewithdir(char *dir);
47 extern void os_warnMD5mismatch(void);
48 extern void os_closetypedfile(FILE *outfile);
49
50 extern int part_depth(struct part *part);
51 extern void part_ungets(char *s, struct part *part);
52 extern void part_close(struct part *part);
53 extern int part_fill(struct part *part);
54 extern void part_addboundary(struct part *part, char *boundary);
55 extern int part_readboundary(struct part *part);
56
57 /* The possible content transfer encodings */
58 enum encoding { enc_none, enc_qp, enc_base64 };
59
60 char *ParseHeaders(struct part *inpart, char **subjectp, char **contentTypep, enum encoding *contentEncodingp, char **contentDispositionp, char **contentMD5p);
61 enum encoding parseEncoding(char *s);
62 params ParseContent(char **headerp);
63 char *getParam(params cParams, char *key);
64 char *getDispositionFilename(char *disposition);
65 void from64(struct part *inpart, FILE *outfile, char **digestp, int suppressCR);
66 void fromqp(struct part *inpart, FILE *outfile, char **digestp);
67 void fromnone(struct part *inpart, FILE *outfile, char **digestp);
68 int handlePartial(struct part *inpart, char *headers, params contentParams,
69 int extractText);
70 int ignoreMessage(struct part *inpart);
71 int handleMultipart(struct part *inpart, char *contentType,
72 params contentParams, int extractText);
73 int handleUuencode(struct part *inpart, char *subject, int extractText);
74 int handleText(struct part *inpart, enum encoding contentEncoding);
75 int saveToFile(struct part *inpart, int inAppleDouble, char *contentType,
76 params contentParams, enum encoding contentEncoding,
77 char *contentDisposition, char *contentMD5);
78
79 /*
80 * Read and handle an RFC 822 message from the body-part 'inpart'.
81 */
handleMessage(struct part * inpart,char * defaultContentType,int inAppleDouble,int extractText)82 int handleMessage(struct part *inpart, char *defaultContentType, int inAppleDouble, int extractText)
83 {
84 char *headers, *subject, *contentType, *contentDisposition, *contentMD5;
85 enum encoding contentEncoding;
86 params contentParams;
87
88 /* Parse the headers, getting the ones we're interested in */
89 headers = ParseHeaders(inpart, &subject, &contentType, &contentEncoding,
90 &contentDisposition, &contentMD5);
91 if (!headers) return 1;
92
93 /* If no content type, or a non-MIME content type, use the default */
94 if (!contentType || !strchr(contentType, '/')) {
95 contentType = defaultContentType;
96 }
97 contentParams = ParseContent(&contentType);
98
99 if (!strcasecmp(contentType, "message/rfc822")) {
100 if (contentEncoding != enc_none) {
101 warn("ignoring invalid content encoding on message/rfc822");
102 }
103
104 /* Simple recursion */
105 return handleMessage(inpart, "text/plain", 0, extractText);
106 }
107 else if (!strcasecmp(contentType, "message/partial")) {
108 if (contentEncoding != enc_none) {
109 warn("ignoring invalid content encoding on message/partial");
110 }
111 return handlePartial(inpart, headers, contentParams, extractText);
112 }
113 else if (!strncasecmp(contentType, "message/", 8)) {
114 /* Probably message/external. We don't care--toss it */
115 return ignoreMessage(inpart);
116 }
117 else if (!strncasecmp(contentType, "multipart/", 10)) {
118 if (contentEncoding != enc_none) {
119 warn("ignoring invalid content encoding on multipart");
120 }
121 return handleMultipart(inpart, contentType, contentParams,
122 extractText);
123 }
124 else if (part_depth(inpart) == 0 &&
125 !strncasecmp(contentType, "text/", 5) &&
126 contentEncoding == enc_none &&
127 !getDispositionFilename(contentDisposition) &&
128 !getParam(contentParams, "name")) {
129 /* top-level text message, handle as possible uuencoded file */
130 return handleUuencode(inpart, subject, extractText);
131 }
132 else if (!extractText && !inAppleDouble &&
133 !strncasecmp(contentType, "text/", 5) &&
134 !getDispositionFilename(contentDisposition) &&
135 !getParam(contentParams, "name")) {
136 return handleText(inpart, contentEncoding);
137 }
138 else {
139 /* Some sort of attachment, extract it */
140 return saveToFile(inpart, inAppleDouble, contentType, contentParams,
141 contentEncoding, contentDisposition, contentMD5);
142 }
143 }
144
145 /*
146 * Skip whitespace and RFC-822 comments.
147 */
SkipWhitespace(char ** s)148 void SkipWhitespace(char **s)
149 {
150 char *p = *s;
151 int commentlevel = 0;
152
153 while (*p && (isspace(*p) || *p == '(')) {
154 if (*p == '\n') {
155 p++;
156 if (*p != ' ' && *p != '\t') {
157 *s = 0;
158 return;
159 }
160 }
161 else if (*p == '(') {
162 p++;
163 commentlevel++;
164 while (commentlevel) {
165 switch (*p) {
166 case '\n':
167 p++;
168 if (*p == ' ' || *p == '\t') break;
169 /* FALL THROUGH */
170 case '\0':
171 *s = 0;
172 return;
173
174 case '\\':
175 p++;
176 break;
177
178 case '(':
179 commentlevel++;
180 break;
181
182 case ')':
183 commentlevel--;
184 break;
185 }
186 p++;
187 }
188 }
189 else p++;
190 }
191 if (*p == 0) {
192 *s = 0;
193 }
194 else {
195 *s = p;
196 }
197 }
198
199 /*
200 * Read and parse the headers of an RFC 822 message, returning them in
201 * a pointer to a static buffer. The headers are read from 'inpart'.
202 * A pointer to the value of any Subject:, Content-Type:,
203 * Content-Disposition:, or Content-MD5: header is stored in the space
204 * pointed to by 'subjectp', 'contentTypep', contentDispositionp, and
205 * contentMD5p, respectively. The Content-Transfer-Encoding is stored
206 * in the enum pointed to by 'contentEncodingp'.
207 */
208 #define HEADGROWSIZE 1000
ParseHeaders(struct part * inpart,char ** subjectp,char ** contentTypep,enum encoding * contentEncodingp,char ** contentDispositionp,char ** contentMD5p)209 char *ParseHeaders(struct part *inpart, char **subjectp, char **contentTypep, enum encoding *contentEncodingp, char **contentDispositionp, char **contentMD5p)
210 {
211 static int alloced = 0;
212 static char *headers;
213 int left, len, i;
214 char *next, *val;
215
216 /* Read headers into buffer pointed to by "headers" */
217 if (!alloced) {
218 headers = xmalloc(alloced = HEADGROWSIZE);
219 }
220 next = headers;
221 *next++ = '\n'; /* Leading newline to make matching header names easier */
222 left = alloced - 2; /* Allow room for terminating null */
223
224 while (part_gets(next, left, inpart) && (*next != '\n' || next[-1] != '\n')) {
225 len = strlen(next);
226
227 if (next[-1] == '\n') {
228 /* Check for valid header-ness of "next" */
229 for (i = 0; i < len; i++) {
230 if (next[i] == ':' ||
231 next[i] <= ' ' || next[i] >= '\177') break;
232 }
233 if (i == 0 || next[i] != ':') {
234 /* Check for header continuation line */
235 if (next == headers+1 || (next[0] != ' ' && next[0] != '\t')) {
236 /*
237 * Not a valid header, push back on input stream
238 * and stop reading input.
239 */
240 part_ungets(next, inpart);
241 break;
242 }
243 }
244 }
245
246 left -= len;
247 next += len;
248
249 if (left < 100) {
250 len = next - headers;
251 alloced += HEADGROWSIZE;
252 left += HEADGROWSIZE;
253 headers = xrealloc(headers, alloced);
254 next = headers + len;
255 }
256 }
257
258 *next = '\0';
259
260 /* Look for the headers we find particularly interesting */
261 *subjectp = *contentTypep = *contentDispositionp = *contentMD5p = 0;
262 *contentEncodingp = enc_none;
263 for (next = headers; *next; next++) {
264 if (*next == '\n') {
265 switch(next[1]) {
266 case 's':
267 case 'S':
268 if (!strncasecmp(next+2, "ubject:", 7)) {
269 val = next+9;
270 SkipWhitespace(&val);
271 if (val) *subjectp = val;
272 }
273 break;
274
275 case 'c':
276 case 'C':
277 if (!strncasecmp(next+2, "ontent-type:", 12)) {
278 val = next+14;
279 SkipWhitespace(&val);
280 if (val) *contentTypep = val;
281 }
282 else if (!strncasecmp(next+2, "ontent-transfer-encoding:", 25)) {
283 *contentEncodingp = parseEncoding(next+27);
284 }
285 else if (!strncasecmp(next+2, "ontent-disposition:", 19)) {
286 val = next+21;
287 SkipWhitespace(&val);
288 if (val) *contentDispositionp = val;
289 }
290 else if (!strncasecmp(next+2, "ontent-md5:", 11)) {
291 val = next+13;
292 SkipWhitespace(&val);
293 if (val) *contentMD5p = val;
294 }
295 }
296 }
297 }
298 return headers;
299 }
300
301 /*
302 * Parse the Content-Transfer-Encoding: value pointed to by 's'.
303 * Returns the appropriate encoding enum.
304 */
parseEncoding(char * s)305 enum encoding parseEncoding(char *s)
306 {
307 SkipWhitespace(&s);
308 if (s) {
309 switch (*s) {
310 case 'q':
311 case 'Q':
312 if (!strncasecmp(s+1, "uoted-printable", 15) &&
313 (isspace(s[16]) || s[16] == '(')) {
314 return enc_qp;
315 }
316 break;
317
318 case '7':
319 case '8':
320 if (!strncasecmp(s+1, "bit", 3) &&
321 (isspace(s[4]) || s[4] == '(')) {
322 return enc_none;
323 }
324 break;
325
326 case 'b':
327 case 'B':
328 if (!strncasecmp(s+1, "ase64", 5) &&
329 (isspace(s[6]) || s[6] == '(')) {
330 return enc_base64;
331 }
332 if (!strncasecmp(s+1, "inary", 5) &&
333 (isspace(s[6]) || s[6] == '(')) {
334 return enc_none;
335 }
336 }
337 warn("ignoring unknown content transfer encoding\n");
338 }
339 return enc_none;
340 }
341
342 /*
343 * Parse the value of a Content-Type: header.
344 * 'headerp' points to a pointer to the input string.
345 * The pointer pointed to by 'headerp' is changed to point to
346 * a static buffer containing the content type stripped of whitespace
347 * and parameters. The parameters are converted to a type suitable for
348 * getParm() and returned.
349 */
350 #define PARAMGROWSIZE 10
ParseContent(char ** headerp)351 params ParseContent(char **headerp)
352 {
353 char *header;
354 static int palloced = 0;
355 static char **param;
356 static int calloced = 0;
357 static char *cbuf;
358 char *p;
359 int nparam;
360
361 p = header = *headerp;
362
363 /* Find end of header, including continuation lines */
364 do {
365 p = strchr(p+1, '\n');
366 } while (p && isspace(p[1]));
367 if (!p) {
368 p = header + strlen(header);
369 }
370
371 /* If necessary, allocate/grow cbuf to hold header. */
372 if (p - header >= calloced) {
373 calloced = p - header + 1;
374 if (calloced < 200) calloced = 200;
375 cbuf = xrealloc(cbuf, calloced);
376 }
377
378 /* Copy header to cbuf */
379 strncpy(cbuf, header, p - header);
380 cbuf[p - header] = 0;
381 header = *headerp = cbuf;
382
383 nparam = 0;
384
385 /* Strip whitespace from content type */
386 /* ParseHeaders() stripped leading whitespace */
387 p = header;
388 while (header && *header && *header != ';') {
389 while (*header && !isspace(*header) && *header != '(' &&
390 *header != ';') {
391 *p++ = *header++;
392 }
393 SkipWhitespace(&header);
394 }
395 if (!header || !*header) return 0;
396 header++;
397 *p = '\0';
398
399 /* Parse the parameters */
400 while (*header) {
401 SkipWhitespace(&header);
402 if (!header) break;
403
404 if (nparam+1 >= palloced) {
405 palloced += PARAMGROWSIZE;
406 param = (char **) xrealloc((char *)param, palloced * sizeof(char *));
407 }
408 param[nparam++] = header;
409
410 /* Find any separating semicolon. Pay attention to quoted-strings */
411 while (*header && *header != ';') {
412 if (*header == '\"') {
413 ++header;
414 while (*header && *header != '\"') {
415 if (*header == '\\') {
416 ++header;
417 if (!*header) break;
418 }
419 ++header;
420 }
421 if (!*header) break;
422 }
423 else if (*header == '(') {
424 /* Convert comments to spaces */
425 p = header;
426 SkipWhitespace(&p);
427 if (!p) {
428 break;
429 }
430 while (header < p) *header++ = ' ';
431 header--;
432 }
433 header++;
434 }
435 if (*header) *header++ = '\0';
436 }
437
438 if (nparam == 0)
439 return 0;
440
441 param[nparam] = 0;
442 return param;
443 }
444
445 /*
446 * Get the value of the parameter named 'key' from the content-type
447 * parameters 'cParams'. Returns a pointer to a static bufer which
448 * contains the value, or null if no such parameter was found.
449 */
450 #define VALUEGROWSIZE 100
getParam(params cParams,char * key)451 char *getParam(params cParams, char *key)
452 {
453 static char *value;
454 static int alloced = 0;
455 int left;
456 int keylen = strlen(key);
457 char *from, *to;
458
459 if (!cParams) return 0;
460
461 if (!alloced) {
462 value = xmalloc(alloced = VALUEGROWSIZE);
463 }
464
465 /* Find the named parameter */
466 while (*cParams) {
467 if (!strncasecmp(key, *cParams, keylen) &&
468 ((*cParams)[keylen] == '=' || isspace((*cParams)[keylen]))) break;
469 cParams++;
470 }
471 if (!*cParams) return 0;
472
473 /* Skip over the "=" and any surrounding whitespace */
474 from = *cParams + keylen;
475 while (*from && isspace(*from)) from++;
476 if (*from++ != '=') return 0;
477 while (*from && isspace(*from)) from++;
478 if (!*from) return 0;
479
480 /* Copy value into buffer */
481 to = value;
482 left = alloced - 1;
483 if (*from == '\"') {
484 /* Quoted-string */
485 from++;
486 while (*from && *from != '\"') {
487 if (!--left) {
488 alloced += VALUEGROWSIZE;
489 left += VALUEGROWSIZE;
490 value = xrealloc(value, alloced);
491 to = value + alloced - left - 2;
492 }
493 if (*from == '\\') {
494 from++;
495 if (!*from) return 0;
496 }
497 *to++ = *from++;
498 }
499 if (!*from) return 0;
500 }
501 else {
502 /* Just a token */
503 while (*from && !isspace(*from)) {
504 if (!--left) {
505 alloced += VALUEGROWSIZE;
506 left += VALUEGROWSIZE;
507 value = xrealloc(value, alloced);
508 to = value + alloced - left - 2;
509 }
510 *to++ = *from++;
511 }
512 }
513 *to = '\0';
514 return value;
515 }
516
517 /*
518 * Get the value of the "filename" parameter in a Content-Disposition:
519 * header. Returns a pointer to a static buffer containing the value, or
520 * a null pointer if there was no such parameter.
521 */
522 char *
getDispositionFilename(char * disposition)523 getDispositionFilename(char *disposition)
524 {
525 static char *value;
526 static int alloced = 0;
527 int left;
528 char *to;
529
530 if (!disposition) return 0;
531
532 /* Skip until we find ";" "filename" "=" tokens. */
533 for (;;) {
534 /* Skip until we find ";" */
535 while (*disposition != ';') {
536 if (!*disposition) return 0;
537 else if (*disposition == '\"') {
538 ++disposition;
539 while (*disposition && *disposition != '\"') {
540 if (*disposition == '\\') {
541 ++disposition;
542 if (!*disposition) return 0;
543 }
544 ++disposition;
545 }
546 if (!*disposition) return 0;
547 }
548 else if (*disposition == '(') {
549 SkipWhitespace(&disposition);
550 if (!disposition) return 0;
551 disposition--;
552 }
553 disposition++;
554 }
555
556 /* Skip over ";" and trailing whitespace */
557 disposition++;
558 SkipWhitespace(&disposition);
559 if (!disposition) return 0;
560
561 /*
562 * If we're not looking at a "filename" token, go back
563 * and look for another ";". Otherwise skip it and
564 * trailing whitespace.
565 */
566 if (strncasecmp(disposition, "filename", 8) != 0) continue;
567 disposition += 8;
568 if (!isspace(*disposition) && *disposition != '=' &&
569 *disposition != '(') {
570 continue;
571 }
572 SkipWhitespace(&disposition);
573 if (!disposition) return 0;
574
575 /* If we're looking at a ";", we found what we're looking for */
576 if (*disposition++ == ';') break;
577 }
578
579 SkipWhitespace(&disposition);
580 if (!disposition) return 0;
581
582 if (!alloced) {
583 value = xmalloc(alloced = VALUEGROWSIZE);
584 }
585
586 /* Copy value into buffer */
587 to = value;
588 left = alloced - 1;
589 if (*disposition == '\"') {
590 /* Quoted-string */
591 disposition++;
592 while (*disposition && *disposition != '\"') {
593 if (!--left) {
594 alloced += VALUEGROWSIZE;
595 left += VALUEGROWSIZE;
596 value = xrealloc(value, alloced);
597 to = value + alloced - left - 2;
598 }
599 if (*disposition == '\\') {
600 disposition++;
601 if (!*disposition) return 0;
602 }
603 *to++ = *disposition++;
604 }
605 if (!*disposition) return 0;
606 }
607 else {
608 /* Just a token */
609 while (*disposition && !isspace(*disposition) &&
610 *disposition != '(') {
611 if (!--left) {
612 alloced += VALUEGROWSIZE;
613 left += VALUEGROWSIZE;
614 value = xrealloc(value, alloced);
615 to = value + alloced - left - 2;
616 }
617 *to++ = *disposition++;
618 }
619 }
620 *to = '\0';
621 return value;
622 }
623
624 /*
625 * Read and handle a message/partial object from the file 'inpart'.
626 */
handlePartial(struct part * inpart,char * headers,params contentParams,int extractText)627 int handlePartial(struct part *inpart, char *headers, params contentParams, int extractText)
628 {
629 char *id, *dir, *p;
630 int thispart;
631 int nparts = 0;
632 char buf[1024];
633 FILE *partfile, *outfile;
634 struct part *outpart;
635 int i, docopy;
636
637 id = getParam(contentParams, "id");
638 if (!id) {
639 warn("partial message has no id parameter");
640 goto ignore;
641 }
642
643 /* Get directory to store the parts being reassembled */
644 dir = os_idtodir(id);
645 if (!dir) goto ignore;
646
647 p = getParam(contentParams, "number");
648 if (!p) {
649 warn("partial message doesn't have number parameter");
650 goto ignore;
651 }
652 thispart = atoi(p);
653
654 if ((p = getParam(contentParams, "total"))) {
655 nparts = atoi(p);
656 if (nparts <= 0) {
657 warn("partial message has invalid number of parts");
658 goto ignore;
659 }
660 /* Store number of parts in reassembly directory */
661 sprintf(buf, "%sCT", dir);
662 partfile = os_createfile(buf);
663 if (!partfile) {
664 os_perror(buf);
665 goto ignore;
666 }
667 fprintf(partfile, "%d\n", nparts);
668 fclose(partfile);
669 }
670 else {
671 /* Try to retrieve number of parts from reassembly directory */
672 sprintf(buf, "%sCT", dir);
673 if ((partfile = fopen(buf, "r"))) {
674 if (fgets(buf, sizeof(buf), partfile)) {
675 nparts = atoi(buf);
676 if (nparts < 0) nparts = 0;
677 }
678 fclose(partfile);
679 }
680 }
681
682 /* Sanity check */
683 if (thispart <= 0 || (nparts && thispart > nparts)) {
684 warn("partial message has invalid number");
685 goto ignore;
686 }
687
688 sprintf(buf, "Saving part %d ", thispart);
689 if (nparts) sprintf(buf+strlen(buf), "of %d ", nparts);
690 strcat(buf, getParam(contentParams, "id"));
691 chat(buf);
692
693 /* Create file to store this part */
694 sprintf(buf, "%s%d", dir, thispart);
695 partfile = os_createnewfile(buf);
696 if (!partfile) {
697 os_perror(buf);
698 goto ignore;
699 }
700
701 /* Do special-case header handling for first part */
702 if (thispart == 1) {
703 int skippedfirstbyte = 0;
704
705 while (*headers) {
706 if (*headers == '\n' &&
707 (!strncasecmp(headers, "\ncontent-", 9) ||
708 !strncasecmp(headers, "\nmessage-id:", 12))) {
709 /* Special case, skip header */
710 headers++;
711 while (*headers && (*headers != '\n' || isspace(headers[1]))) {
712 headers++;
713 }
714 }
715 else {
716 /* First byte of headers is extra newline, don't write it to file */
717 if (skippedfirstbyte++) putc(*headers, partfile);
718 headers++;
719 }
720 }
721 docopy = 0;
722 /* Handle headers in the multipart/partial body */
723 while (part_gets(buf, sizeof(buf), inpart)) {
724 if (*buf == '\n') {
725 putc('\n', partfile);
726 break;
727 }
728 if (!strncasecmp(buf, "content-", 8) || !strncasecmp(buf, "message-id:", 11)) {
729 docopy = 1;
730 }
731 else if (!isspace(*buf)) {
732 docopy = 0;
733 }
734
735 if (docopy) fputs(buf, partfile);
736 while(buf[strlen(buf)-1] != '\n' && part_gets(buf, sizeof(buf), inpart)) {
737 if (docopy) fputs(buf, partfile);
738 }
739 }
740 }
741
742 /* Copy the contents to the file */
743 while (part_gets(buf, sizeof(buf), inpart)) {
744 fputs(buf, partfile);
745 }
746 fclose(partfile);
747
748 /* Check to see if we have all parts. Start from the highest numbers
749 * as we are more likely not to have them.
750 */
751 for (i = nparts; i; i--) {
752 sprintf(buf, "%s%d", dir, i);
753 partfile = fopen(buf, "r");
754 if (partfile) {
755 fclose(partfile);
756 }
757 else {
758 break;
759 }
760 }
761
762 if (i || !nparts) {
763 /* We don't have all the parts yet */
764 return 0;
765 }
766
767 /* We have everything, concatenate all the parts into a single file */
768 sprintf(buf, "%sFULL", dir);
769 outfile = os_createnewfile(buf);
770 if (!outfile) {
771 os_perror(buf);
772 return 1;
773 }
774 for (i=1; i<=nparts; i++) {
775 sprintf(buf, "%s%d", dir, i);
776 partfile = fopen(buf, "r");
777 if (!partfile) {
778 os_perror(buf);
779 return 1;
780 }
781 while (fgets(buf, sizeof(buf), partfile)) {
782 fputs(buf, outfile);
783 }
784 fclose(partfile);
785
786 /* Done with message part file, delete it */
787 sprintf(buf, "%s%d", dir, i);
788 remove(buf);
789 }
790
791 /* Open the concatenated file for reading and handle it */
792 fclose(outfile);
793 sprintf(buf, "%sFULL", dir);
794 outfile = fopen(buf, "r");
795 if (!outfile) {
796 os_perror(buf);
797 return 1;
798 }
799 outpart = part_init(outfile);
800 handleMessage(outpart, "text/plain", 0, extractText);
801 part_close(outpart);
802
803 /* Clean up the rest of the reassembly directory */
804 sprintf(buf, "%sFULL", dir);
805 remove(buf);
806 sprintf(buf, "%sCT", dir);
807 remove(buf);
808 os_donewithdir(dir);
809
810 return 0;
811
812 ignore:
813 ignoreMessage(inpart);
814 return 1;
815 }
816
817 /*
818 * Skip over a message object from the file 'inpart'.
819 */
ignoreMessage(struct part * inpart)820 int ignoreMessage(struct part *inpart)
821 {
822 while (part_getc(inpart) != EOF);
823 return 0;
824 }
825
826 /*
827 * Read and handle a multipart object from 'inpart'.
828 */
handleMultipart(struct part * inpart,char * contentType,params contentParams,int extractText)829 int handleMultipart(struct part *inpart, char *contentType, params contentParams, int extractText)
830 {
831 char *id;
832 char *defaultContentType = "text/plain";
833 int isAppleDouble = 0;
834
835 /* Components of multipart/digest have a different default content-type */
836 if (!strcasecmp(contentType, "multipart/digest")) {
837 defaultContentType = "message/rfc822";
838 }
839 if (!strcasecmp(contentType, "multipart/appledouble")) {
840 isAppleDouble++;
841 }
842
843 if (!(id = getParam(contentParams, "boundary"))) {
844 warn("multipart message has no boundary parameter");
845 id="";
846 }
847
848 /* Add the new boundary id */
849 part_addboundary(inpart, id);
850
851 #ifdef __riscos
852 /*
853 * "Marcel" encodes RISCOS directory structure in the multipart
854 * structure. That is the Wrong Way to do it, but we hold our
855 * nose and pass the information to the OS layer.
856 */
857 os_boundaryhookopen(part_depth(inpart));
858 #endif
859
860 /*
861 * Skip over preamble.
862 * HACK: The initial boundary doesn't have to start with a newline,
863 * so we deal with this by stuffing an initial newline into the input
864 * stream
865 */
866 part_ungetc('\n', inpart);
867 ignoreMessage(inpart);
868
869 /* Handle the component messages */
870 while (!part_readboundary(inpart)) {
871 handleMessage(inpart, defaultContentType, isAppleDouble, extractText);
872 }
873
874 #ifdef __riscos
875 os_boundaryhookclose(part_depth(inpart));
876 #endif
877
878 /* Skip over postamble */
879 ignoreMessage(inpart);
880
881 /* Remove any lingering unused description file */
882 (void) remove(TEMPFILENAME);
883
884 return 0;
885 }
886
887 /*
888 * Handle a text message object from 'inpart' by saving it to
889 * the temporary description file.
890 */
handleText(struct part * inpart,enum encoding contentEncoding)891 int handleText(struct part *inpart, enum encoding contentEncoding)
892 {
893 FILE *descfile;
894
895 descfile = os_createnewfile(TEMPFILENAME);
896 if (!descfile) {
897 os_perror(TEMPFILENAME);
898 ignoreMessage(inpart);
899 return 1;
900 }
901
902 /* Write the file, handling the appropriate encoding */
903 switch (contentEncoding) {
904 case enc_none:
905 fromnone(inpart, descfile, (char **)0);
906 break;
907
908 case enc_qp:
909 fromqp(inpart, descfile, (char **)0);
910 break;
911
912 case enc_base64:
913 from64(inpart, descfile, (char **)0, 1);
914 break;
915 }
916
917 fclose(descfile);
918 return 0;
919 }
920
921 /*
922 * Read a message object from 'inpart' and save it to a file.
923 */
saveToFile(struct part * inpart,int inAppleDouble,char * contentType,params contentParams,enum encoding contentEncoding,char * contentDisposition,char * contentMD5)924 int saveToFile(struct part *inpart, int inAppleDouble, char *contentType, params contentParams, enum encoding contentEncoding, char *contentDisposition, char *contentMD5)
925 {
926 FILE *outfile = 0;
927 int flags = 0;
928 int suppressCR = 0;
929 char *outputmd5;
930 char *fname;
931
932 if (!strncasecmp(contentType, "text/", 5)) {
933 suppressCR = 1;
934 }
935 else if (contentEncoding == enc_base64) {
936 /*
937 * HEURISTIC: It is not in general possible to determine whether
938 * any non-text content type is line-oriented. We guess
939 * the "binary" status of a part from the composer's choice
940 * of content transfer encoding.
941 *
942 * If the content transfer encoding is "binary" and the input is
943 * not line-oriented, we're screwed anyway--the input file has
944 * been opened in text mode. So the "binary output file" heuristic
945 * is not applied in this case.
946 */
947 flags |= FILE_BINARY;
948 }
949
950 if (inAppleDouble) flags |= FILE_INAPPLEDOUBLE;
951
952 /* Find an appropriate filename and create the output file */
953 fname = getDispositionFilename(contentDisposition);
954 if (!fname) fname = getParam(contentParams, "name");
955 if (fname) fname = strsave(fname);
956 outfile = os_newtypedfile(fname, contentType, flags, contentParams);
957 if (fname) free(fname);
958 if (!outfile) {
959 ignoreMessage(inpart);
960 return 1;
961 }
962
963 /* Write the file, handling the appropriate encoding */
964 switch (contentEncoding) {
965 case enc_none:
966 fromnone(inpart, outfile, &outputmd5);
967 break;
968
969 case enc_qp:
970 fromqp(inpart, outfile, &outputmd5);
971 break;
972
973 case enc_base64:
974 from64(inpart, outfile, &outputmd5, suppressCR);
975 break;
976 }
977 rewind(outfile);
978
979 /* Check the MD5 digest if it was supplied */
980 if (contentMD5) {
981 if (strncmp(outputmd5, contentMD5, strlen(outputmd5)) != 0) {
982 os_warnMD5mismatch();
983 }
984 }
985 free(outputmd5);
986
987 os_closetypedfile(outfile);
988 return 0;
989 }
990
991 #define XX 127
992 /*
993 * Table for decoding hexadecimal in quoted-printable
994 */
995 static char index_hex[256] = {
996 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
997 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
998 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
999 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,XX,XX, XX,XX,XX,XX,
1000 XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1001 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1002 XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1003 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1004 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1005 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1006 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1007 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1008 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1009 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1010 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1011 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1012 };
1013 #define HEXCHAR(c) (index_hex[(unsigned char)(c)])
1014
1015 /*
1016 * Table for decoding base64
1017 */
1018 static char index_64[256] = {
1019 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1020 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1021 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63,
1022 52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
1023 XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
1024 15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
1025 XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
1026 41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
1027 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1028 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1029 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1030 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1031 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1032 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1033 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1034 XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
1035 };
1036 #define CHAR64(c) (index_64[(unsigned char)(c)])
1037
from64(struct part * inpart,FILE * outfile,char ** digestp,int suppressCR)1038 void from64(struct part *inpart, FILE *outfile, char **digestp, int suppressCR)
1039 {
1040 int c1, c2, c3, c4;
1041 int DataDone = 0;
1042 char buf[3];
1043 MD5_CTX context;
1044
1045 if (digestp) MD5Init(&context);
1046 while ((c1 = part_getc(inpart)) != EOF) {
1047 if (c1 != '=' && CHAR64(c1) == XX) {
1048 continue;
1049 }
1050 if (DataDone) continue;
1051 do {
1052 c2 = part_getc(inpart);
1053 } while (c2 != EOF && c2 != '=' && CHAR64(c2) == XX);
1054 do {
1055 c3 = part_getc(inpart);
1056 } while (c3 != EOF && c3 != '=' && CHAR64(c3) == XX);
1057 do {
1058 c4 = part_getc(inpart);
1059 } while (c4 != EOF && c4 != '=' && CHAR64(c4) == XX);
1060 if (c2 == EOF || c3 == EOF || c4 == EOF) {
1061 warn("Premature EOF");
1062 break;
1063 }
1064 if (c1 == '=' || c2 == '=') {
1065 DataDone=1;
1066 continue;
1067 }
1068 c1 = CHAR64(c1);
1069 c2 = CHAR64(c2);
1070 buf[0] = ((c1<<2) | ((c2&0x30)>>4));
1071 if (!suppressCR || buf[0] != '\r') putc(buf[0], outfile);
1072 if (c3 == '=') {
1073 if (digestp) MD5Update(&context, buf, 1);
1074 DataDone = 1;
1075 } else {
1076 c3 = CHAR64(c3);
1077 buf[1] = (((c2&0x0F) << 4) | ((c3&0x3C) >> 2));
1078 if (!suppressCR || buf[1] != '\r') putc(buf[1], outfile);
1079 if (c4 == '=') {
1080 if (digestp) MD5Update(&context, buf, 2);
1081 DataDone = 1;
1082 } else {
1083 c4 = CHAR64(c4);
1084 buf[2] = (((c3&0x03) << 6) | c4);
1085 if (!suppressCR || buf[2] != '\r') putc(buf[2], outfile);
1086 if (digestp) MD5Update(&context, buf, 3);
1087 }
1088 }
1089 }
1090 if (digestp) *digestp = md5contextTo64(&context);
1091 }
1092
fromqp(struct part * inpart,FILE * outfile,char ** digestp)1093 void fromqp(struct part *inpart, FILE *outfile, char **digestp)
1094 {
1095 int c1, c2;
1096 MD5_CTX context;
1097 char c;
1098
1099 if (digestp) MD5Init(&context);
1100
1101 while ((c1 = part_getc(inpart)) != EOF) {
1102 if (c1 == '=') {
1103 c1 = part_getc(inpart);
1104 if (c1 != '\n') {
1105 c1 = HEXCHAR(c1);
1106 c2 = part_getc(inpart);
1107 c2 = HEXCHAR(c2);
1108 c = c1<<4 | c2;
1109 if (c != '\r') putc(c, outfile);
1110 if (digestp) MD5Update(&context, &c, 1);
1111 }
1112 } else {
1113 putc(c1, outfile);
1114 if (c1 == '\n') {
1115 if (digestp) MD5Update(&context, "\r", 1);
1116 }
1117 c = c1;
1118 if (digestp) MD5Update(&context, &c, 1);
1119 }
1120 }
1121 if (digestp) *digestp=md5contextTo64(&context);
1122 }
1123
fromnone(struct part * inpart,FILE * outfile,char ** digestp)1124 void fromnone(struct part *inpart, FILE *outfile, char **digestp)
1125 {
1126 int c;
1127 char ch;
1128 MD5_CTX context;
1129
1130 if (digestp) MD5Init(&context);
1131
1132 while ((c = part_getc(inpart)) != EOF) {
1133 putc(c, outfile);
1134 if (c == '\n') {
1135 if (digestp) MD5Update(&context, "\r", 1);
1136 }
1137 ch = c;
1138 if (digestp) MD5Update(&context, &ch, 1);
1139 }
1140 if (digestp) *digestp=md5contextTo64(&context);
1141 }
1142
1143