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