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