]> diplodocus.org Git - nmh/blob - uip/mhbuildsbr.c
Add new signal include file location for El Capitan. I am not sure
[nmh] / uip / mhbuildsbr.c
1
2 /*
3 * mhbuildsbr.c -- routines to expand/translate MIME composition files
4 *
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
8 */
9
10 /*
11 * This code was originally part of mhn.c. I split it into
12 * a separate program (mhbuild.c) and then later split it
13 * again (mhbuildsbr.c). But the code still has some of
14 * the mhn.c code in it. This program needs additional
15 * streamlining and removal of unneeded code.
16 */
17
18 #include <h/mh.h>
19 #include <fcntl.h>
20 #include <h/md5.h>
21 #include <h/mts.h>
22 #include <h/tws.h>
23 #include <h/mime.h>
24 #include <h/mhparse.h>
25 #include <h/utils.h>
26
27 #ifdef HAVE_SYS_TIME_H
28 # include <sys/time.h>
29 #endif
30 #include <time.h>
31
32
33 extern int debugsw;
34
35 extern int listsw;
36 extern int rfc934sw;
37 extern int contentidsw;
38
39 /* cache policies */
40 extern int rcachesw; /* mhcachesbr.c */
41 extern int wcachesw; /* mhcachesbr.c */
42
43 static char prefix[] = "----- =_aaaaaaaaaa";
44
45 struct attach_list {
46 char *filename;
47 struct attach_list *next;
48 };
49
50 typedef struct convert_list {
51 char *type;
52 char *filename;
53 char *argstring;
54 struct convert_list *next;
55 } convert_list;
56
57 /*
58 * Maximum size of URL token in message/external-body
59 */
60
61 #define MAXURLTOKEN 40
62
63
64 /* mhmisc.c */
65 void content_error (char *, CT, char *, ...);
66
67 /* mhcachesbr.c */
68 int find_cache (CT, int, int *, char *, char *, int);
69
70 /* mhfree.c */
71 extern CT *cts;
72 void freects_done (int) NORETURN;
73 void free_ctinfo (CT);
74 void free_encoding (CT, int);
75
76 /*
77 * static prototypes
78 */
79 static int init_decoded_content (CT, const char *);
80 static void setup_attach_content(CT, char *);
81 static void set_disposition (CT);
82 static void set_charset (CT, int);
83 static void expand_pseudoheaders (CT, struct multipart *, const char *,
84 const convert_list *);
85 static void expand_pseudoheader (CT, CT *, struct multipart *, const char *,
86 const char *, const char *);
87 static char *fgetstr (char *, int, FILE *);
88 static int user_content (FILE *, char *, CT *, const char *infilename);
89 static void set_id (CT, int);
90 static int compose_content (CT, int);
91 static int scan_content (CT, size_t);
92 static int build_headers (CT, int);
93 static char *calculate_digest (CT, int);
94 static int extract_headers (CT, char *, FILE **);
95
96
97 static unsigned char directives_stack[32];
98 static unsigned int directives_index;
99
100 static int do_direct(void)
101 {
102 return directives_stack[directives_index];
103 }
104
105 static void directive_onoff(int onoff)
106 {
107 if (directives_index >= sizeof(directives_stack) - 1) {
108 fprintf(stderr, "mhbuild: #on/off overflow, continuing\n");
109 return;
110 }
111 directives_stack[++directives_index] = onoff;
112 }
113
114 static void directive_init(int onoff)
115 {
116 directives_index = 0;
117 directives_stack[0] = onoff;
118 }
119
120 static void directive_pop(void)
121 {
122 if (directives_index > 0)
123 directives_index--;
124 else
125 fprintf(stderr, "mhbuild: #pop underflow, continuing\n");
126 }
127
128 /*
129 * Main routine for translating composition file
130 * into valid MIME message. It translates the draft
131 * into a content structure (actually a tree of content
132 * structures). This message then can be manipulated
133 * in various ways, including being output via
134 * output_message().
135 */
136
137 CT
138 build_mime (char *infile, int autobuild, int dist, int directives,
139 int header_encoding, size_t maxunencoded, int verbose)
140 {
141 int compnum, state;
142 char buf[BUFSIZ], name[NAMESZ];
143 char *cp, *np, *vp;
144 struct multipart *m;
145 struct part **pp;
146 CT ct;
147 FILE *in;
148 HF hp;
149 m_getfld_state_t gstate = 0;
150 struct attach_list *attach_head = NULL, *attach_tail = NULL, *at_entry;
151 convert_list *convert_head = NULL, *convert_tail = NULL, *convert;
152
153 directive_init(directives);
154
155 umask (~m_gmprot ());
156
157 /* open the composition draft */
158 if ((in = fopen (infile, "r")) == NULL)
159 adios (infile, "unable to open for reading");
160
161 /*
162 * Allocate space for primary (outside) content
163 */
164 if ((ct = (CT) mh_xcalloc (1, sizeof(*ct))) == NULL)
165 adios (NULL, "out of memory");
166
167 /*
168 * Allocate structure for handling decoded content
169 * for this part. We don't really need this, but
170 * allocate it to remain consistent.
171 */
172 init_decoded_content (ct, infile);
173
174 /*
175 * Parse some of the header fields in the composition
176 * draft into the linked list of header fields for
177 * the new MIME message.
178 */
179 m_getfld_track_filepos (&gstate, in);
180 for (compnum = 1;;) {
181 int bufsz = sizeof buf;
182 switch (state = m_getfld (&gstate, name, buf, &bufsz, in)) {
183 case FLD:
184 case FLDPLUS:
185 compnum++;
186
187 /* abort if draft has Mime-Version or C-T-E header field */
188 if (strcasecmp (name, VRSN_FIELD) == 0 ||
189 strcasecmp (name, ENCODING_FIELD) == 0) {
190 if (autobuild) {
191 fclose(in);
192 free (ct);
193 return NULL;
194 } else {
195 adios (NULL, "draft shouldn't contain %s: field", name);
196 }
197 }
198
199 /* ignore any Content-Type fields in the header */
200 if (!strcasecmp (name, TYPE_FIELD)) {
201 while (state == FLDPLUS) {
202 bufsz = sizeof buf;
203 state = m_getfld (&gstate, name, buf, &bufsz, in);
204 }
205 goto finish_field;
206 }
207
208 /* get copies of the buffers */
209 np = add (name, NULL);
210 vp = add (buf, NULL);
211
212 /* if necessary, get rest of field */
213 while (state == FLDPLUS) {
214 bufsz = sizeof buf;
215 state = m_getfld (&gstate, name, buf, &bufsz, in);
216 vp = add (buf, vp); /* add to previous value */
217 }
218
219 /*
220 * Now add the header data to the list, unless it's an attach
221 * header; in that case, add it to our attach list
222 */
223
224 if (strcasecmp(ATTACH_FIELD, np) == 0 ||
225 strcasecmp(ATTACH_FIELD_ALT, np) == 0) {
226 struct attach_list *entry;
227 char *s = vp, *e = vp + strlen(vp) - 1;
228 free(np);
229
230 /*
231 * Make sure we can find the start of this filename.
232 * If it's blank, we skip completely. Otherwise, strip
233 * off any leading spaces and trailing newlines.
234 */
235
236 while (isspace((unsigned char) *s))
237 s++;
238
239 while (e > s && *e == '\n')
240 *e-- = '\0';
241
242 if (*s == '\0') {
243 free(vp);
244 goto finish_field;
245 }
246
247 entry = mh_xmalloc(sizeof(*entry));
248 entry->filename = getcpy(s);
249 entry->next = NULL;
250 free(vp);
251
252 if (attach_tail) {
253 attach_tail->next = entry;
254 attach_tail = entry;
255 } else {
256 attach_head = attach_tail = entry;
257 }
258 } else if (strncasecmp(MHBUILD_FILE_PSEUDOHEADER, np,
259 strlen (MHBUILD_FILE_PSEUDOHEADER)) == 0) {
260 /* E.g.,
261 * Nmh-mhbuild-file-text/calendar: /home/user/Mail/inbox/9
262 */
263 char *type = np + strlen (MHBUILD_FILE_PSEUDOHEADER);
264 char *filename = vp;
265
266 /* vp should begin with a space because m_getfld()
267 includes the space after the colon in buf. */
268 while (isspace((unsigned char) *filename)) { ++filename; }
269 /* Trim trailing newline and any other whitespace. */
270 rtrim (filename);
271
272 for (convert = convert_head; convert; convert = convert->next) {
273 if (strcasecmp (convert->type, type) == 0) { break; }
274 }
275 if (convert) {
276 if (convert->filename &&
277 strcasecmp (convert->filename, filename)) {
278 adios (NULL, "Multiple %s headers with different files"
279 " not allowed", type);
280 } else {
281 convert->filename = getcpy (filename);
282 }
283 } else {
284 convert = mh_xcalloc (sizeof *convert, 1);
285 convert->filename = getcpy (filename);
286 convert->type = getcpy (type);
287
288 if (convert_tail) {
289 convert_tail->next = convert;
290 } else {
291 convert_head = convert;
292 }
293 convert_tail = convert;
294 }
295
296 free (vp);
297 free (np);
298 } else if (strncasecmp(MHBUILD_ARGS_PSEUDOHEADER, np,
299 strlen (MHBUILD_ARGS_PSEUDOHEADER)) == 0) {
300 /* E.g.,
301 * Nmh-mhbuild-args-text/calendar: -reply accept
302 */
303 char *type = np + strlen (MHBUILD_ARGS_PSEUDOHEADER);
304 char *argstring = vp;
305
306 /* vp should begin with a space because m_getfld()
307 includes the space after the colon in buf. */
308 while (isspace((unsigned char) *argstring)) { ++argstring; }
309 /* Trim trailing newline and any other whitespace. */
310 rtrim (argstring);
311
312 for (convert = convert_head; convert; convert = convert->next) {
313 if (strcasecmp (convert->type, type) == 0) { break; }
314 }
315 if (convert) {
316 if (convert->argstring &&
317 strcasecmp (convert->argstring, argstring)) {
318 adios (NULL, "Multiple %s headers with different "
319 "argstrings not allowed", type);
320 } else {
321 convert->argstring = getcpy (argstring);
322 }
323 } else {
324 convert = mh_xcalloc (sizeof *convert, 1);
325 convert->type = getcpy (type);
326 convert->argstring = getcpy (argstring);
327
328 if (convert_tail) {
329 convert_tail->next = convert;
330 } else {
331 convert_head = convert;
332 }
333 convert_tail = convert;
334 }
335
336 free (vp);
337 free (np);
338 } else {
339 add_header (ct, np, vp);
340 }
341
342 finish_field:
343 /* if this wasn't the last header field, then continue */
344 continue;
345
346 case BODY:
347 fseek (in, (long) (-strlen (buf)), SEEK_CUR);
348 /* fall through */
349 case FILEEOF:
350 break;
351
352 case LENERR:
353 case FMTERR:
354 adios (NULL, "message format error in component #%d", compnum);
355
356 default:
357 adios (NULL, "getfld() returned %d", state);
358 }
359 break;
360 }
361 m_getfld_state_destroy (&gstate);
362
363 /*
364 * Iterate through the list of headers and call the function to MIME-ify
365 * them if required.
366 */
367
368 for (hp = ct->c_first_hf; hp != NULL; hp = hp->next) {
369 if (encode_rfc2047(hp->name, &hp->value, header_encoding, NULL)) {
370 adios(NULL, "Unable to encode header \"%s\"", hp->name);
371 }
372 }
373
374 /*
375 * Now add the MIME-Version header field
376 * to the list of header fields.
377 */
378
379 if (! dist) {
380 np = add (VRSN_FIELD, NULL);
381 vp = concat (" ", VRSN_VALUE, "\n", NULL);
382 add_header (ct, np, vp);
383 }
384
385 /*
386 * We initally assume we will find multiple contents in the
387 * draft. So create a multipart/mixed content to hold everything.
388 * We can remove this later, if it is not needed.
389 */
390 if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
391 done (1);
392 ct->c_type = CT_MULTIPART;
393 ct->c_subtype = MULTI_MIXED;
394
395 if ((m = (struct multipart *) mh_xcalloc (1, sizeof(*m))) == NULL)
396 adios (NULL, "out of memory");
397 ct->c_ctparams = (void *) m;
398 pp = &m->mp_parts;
399
400 /*
401 * read and parse the composition file
402 * and the directives it contains.
403 */
404 while (fgetstr (buf, sizeof(buf) - 1, in)) {
405 struct part *part;
406 CT p;
407
408 if (user_content (in, buf, &p, infile) == DONE) {
409 admonish (NULL, "ignoring spurious #end");
410 continue;
411 }
412 if (!p)
413 continue;
414
415 if ((part = (struct part *) mh_xcalloc (1, sizeof(*part))) == NULL)
416 adios (NULL, "out of memory");
417 *pp = part;
418 pp = &part->mp_next;
419 part->mp_part = p;
420 }
421
422 /*
423 * Add any Attach headers to the list of MIME parts at the end of the
424 * message.
425 */
426
427 for (at_entry = attach_head; at_entry; ) {
428 struct attach_list *at_prev = at_entry;
429 struct part *part;
430 CT p;
431
432 if (access(at_entry->filename, R_OK) != 0) {
433 adios("reading", "Unable to open %s for", at_entry->filename);
434 }
435
436 if ((p = (CT) mh_xcalloc (1, sizeof(*p))) == NULL)
437 adios(NULL, "out of memory");
438
439 init_decoded_content(p, infile);
440
441 /*
442 * Initialize our content structure based on the filename,
443 * and fill in all of the relevant fields. Also place MIME
444 * parameters in the attributes array.
445 */
446
447 setup_attach_content(p, at_entry->filename);
448
449 if ((part = (struct part *) mh_xcalloc (1, sizeof(*part))) == NULL)
450 adios (NULL, "out of memory");
451 *pp = part;
452 pp = &part->mp_next;
453 part->mp_part = p;
454
455 at_entry = at_entry->next;
456 free(at_prev->filename);
457 free(at_prev);
458 }
459
460 /*
461 * Handle the mhbuild pseudoheaders, which deal with specific
462 * content types.
463 */
464 if (convert_head) {
465 CT *ctp;
466 convert_list *next;
467
468 done = freects_done;
469
470 /* In case there are multiple calls that land here, prevent leak. */
471 for (ctp = cts; ctp && *ctp; ++ctp) { free_content (*ctp); }
472 free (cts);
473
474 /* Extract the type part (as a CT) from filename. */
475 if (! (cts = (CT *) mh_xcalloc ((size_t) 2, sizeof *cts))) {
476 adios (NULL, "out of memory");
477 } else if (! (cts[0] = parse_mime (convert_head->filename))) {
478 adios (NULL, "failed to parse %s", convert_head->filename);
479 }
480
481 expand_pseudoheaders (cts[0], m, infile, convert_head);
482
483 /* Free the convert list. */
484 for (convert = convert_head; convert; convert = next) {
485 next = convert->next;
486 free (convert->type);
487 free (convert->filename);
488 free (convert->argstring);
489 free (convert);
490 }
491 convert_head = NULL;
492 }
493
494 /*
495 * To allow for empty message bodies, if we've found NO content at all
496 * yet cook up an empty text/plain part.
497 */
498
499 if (!m->mp_parts) {
500 CT p;
501 struct part *part;
502 struct text *t;
503
504 if ((p = (CT) mh_xcalloc (1, sizeof(*p))) == NULL)
505 adios(NULL, "out of memory");
506
507 init_decoded_content(p, infile);
508
509 if (get_ctinfo ("text/plain", p, 0) == NOTOK)
510 done (1);
511
512 p->c_type = CT_TEXT;
513 p->c_subtype = TEXT_PLAIN;
514 p->c_encoding = CE_7BIT;
515 /*
516 * Sigh. ce_file contains the "decoded" contents of this part.
517 * So this seems like the best option available since we're going
518 * to call scan_content() on this.
519 */
520 p->c_cefile.ce_file = getcpy("/dev/null");
521 p->c_begin = ftell(in);
522 p->c_end = ftell(in);
523
524 if ((t = (struct text *) mh_xcalloc (1, sizeof (*t))) == NULL)
525 adios (NULL, "out of memory");
526
527 t->tx_charset = CHARSET_SPECIFIED;
528 p->c_ctparams = t;
529
530 if ((part = (struct part *) mh_xcalloc (1, sizeof(*part))) == NULL)
531 adios (NULL, "out of memory");
532 *pp = part;
533 part->mp_part = p;
534 }
535
536 /*
537 * close the composition draft since
538 * it's not needed any longer.
539 */
540 fclose (in);
541
542 /*
543 * If only one content was found, then remove and
544 * free the outer multipart content.
545 */
546 if (!m->mp_parts->mp_next) {
547 CT p;
548
549 p = m->mp_parts->mp_part;
550 m->mp_parts->mp_part = NULL;
551
552 /* move header fields */
553 p->c_first_hf = ct->c_first_hf;
554 p->c_last_hf = ct->c_last_hf;
555 ct->c_first_hf = NULL;
556 ct->c_last_hf = NULL;
557
558 free_content (ct);
559 ct = p;
560 } else {
561 set_id (ct, 1);
562 }
563
564 /*
565 * Fill out, or expand directives. Parse and execute
566 * commands specified by profile composition strings.
567 */
568 compose_content (ct, verbose);
569
570 if ((cp = strchr(prefix, 'a')) == NULL)
571 adios (NULL, "internal error(4)");
572
573 /*
574 * Scan the contents. Choose a transfer encoding, and
575 * check if prefix for multipart boundary clashes with
576 * any of the contents.
577 */
578 while (scan_content (ct, maxunencoded) == NOTOK) {
579 if (*cp < 'z') {
580 (*cp)++;
581 } else {
582 if (*++cp == 0)
583 adios (NULL, "giving up trying to find a unique delimiter string");
584 else
585 (*cp)++;
586 }
587 }
588
589 /* Build the rest of the header field structures */
590 if (! dist)
591 build_headers (ct, header_encoding);
592
593 return ct;
594 }
595
596
597 /*
598 * Set up structures for placing unencoded
599 * content when building parts.
600 */
601
602 static int
603 init_decoded_content (CT ct, const char *filename)
604 {
605 ct->c_ceopenfnx = open7Bit; /* since unencoded */
606 ct->c_ceclosefnx = close_encoding;
607 ct->c_cesizefnx = NULL; /* since unencoded */
608 ct->c_encoding = CE_7BIT; /* Seems like a reasonable default */
609 ct->c_file = add(filename, NULL);
610
611 return OK;
612 }
613
614
615 static char *
616 fgetstr (char *s, int n, FILE *stream)
617 {
618 char *cp, *ep;
619 int o_n = n;
620
621 while(1) {
622 for (ep = (cp = s) + o_n; cp < ep; ) {
623 int i;
624
625 if (!fgets (cp, n, stream))
626 return (cp != s ? s : NULL);
627
628 if (cp == s && *cp != '#')
629 return s;
630
631 cp += (i = strlen (cp)) - 1;
632 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
633 break;
634 *cp = '\0';
635 n -= (i - 2);
636 }
637
638 if (strcmp(s, "#on\n") == 0) {
639 directive_onoff(1);
640 } else if (strcmp(s, "#off\n") == 0) {
641 directive_onoff(0);
642 } else if (strcmp(s, "#pop\n") == 0) {
643 directive_pop();
644 } else {
645 break;
646 }
647 }
648
649 return s;
650 }
651
652
653 /*
654 * Parse the composition draft for text and directives.
655 * Do initial setup of Content structure.
656 */
657
658 static int
659 user_content (FILE *in, char *buf, CT *ctp, const char *infilename)
660 {
661 int extrnal, vrsn;
662 char *cp, **ap;
663 char buffer[BUFSIZ];
664 struct multipart *m;
665 struct part **pp;
666 struct stat st;
667 struct str2init *s2i;
668 CI ci;
669 CT ct;
670 CE ce;
671
672 if (buf[0] == '\n' || (do_direct() && strcmp (buf, "#\n") == 0)) {
673 *ctp = NULL;
674 return OK;
675 }
676
677 /* allocate basic Content structure */
678 if ((ct = (CT) mh_xcalloc (1, sizeof(*ct))) == NULL)
679 adios (NULL, "out of memory");
680 *ctp = ct;
681
682 /* allocate basic structure for handling decoded content */
683 init_decoded_content (ct, infilename);
684 ce = &ct->c_cefile;
685
686 ci = &ct->c_ctinfo;
687 set_id (ct, 0);
688
689 /*
690 * Handle inline text. Check if line
691 * is one of the following forms:
692 *
693 * 1) doesn't begin with '#' (implicit directive)
694 * 2) begins with "##" (implicit directive)
695 * 3) begins with "#<"
696 */
697 if (!do_direct() || buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
698 int headers;
699 int inlineD;
700 long pos;
701 char content[BUFSIZ];
702 FILE *out;
703 char *cp;
704
705 if ((cp = m_mktemp2(NULL, invo_name, NULL, &out)) == NULL) {
706 adios("mhbuildsbr", "unable to create temporary file in %s",
707 get_temp_dir());
708 }
709
710 /* use a temp file to collect the plain text lines */
711 ce->ce_file = add (cp, NULL);
712 ce->ce_unlink = 1;
713
714 if (do_direct() && (buf[0] == '#' && buf[1] == '<')) {
715 strncpy (content, buf + 2, sizeof(content));
716 inlineD = 1;
717 goto rock_and_roll;
718 } else {
719 inlineD = 0;
720 }
721
722 /* the directive is implicit */
723 strncpy (content, "text/plain", sizeof(content));
724 headers = 0;
725 strncpy (buffer, (!do_direct() || buf[0] != '#') ? buf : buf + 1, sizeof(buffer));
726 for (;;) {
727 int i;
728
729 if (headers >= 0 && do_direct() && uprf (buffer, DESCR_FIELD)
730 && buffer[i = strlen (DESCR_FIELD)] == ':') {
731 headers = 1;
732
733 again_descr:
734 ct->c_descr = add (buffer + i + 1, ct->c_descr);
735 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
736 adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
737 switch (buffer[0]) {
738 case ' ':
739 case '\t':
740 i = -1;
741 goto again_descr;
742
743 case '#':
744 adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
745 /* NOTREACHED */
746
747 default:
748 break;
749 }
750 }
751
752 if (headers >= 0 && do_direct() && uprf (buffer, DISPO_FIELD)
753 && buffer[i = strlen (DISPO_FIELD)] == ':') {
754 headers = 1;
755
756 again_dispo:
757 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
758 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
759 adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
760 switch (buffer[0]) {
761 case ' ':
762 case '\t':
763 i = -1;
764 goto again_dispo;
765
766 case '#':
767 adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
768 /* NOTREACHED */
769
770 default:
771 break;
772 }
773 }
774
775 if (headers != 1 || buffer[0] != '\n')
776 fputs (buffer, out);
777
778 rock_and_roll:
779 headers = -1;
780 pos = ftell (in);
781 if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
782 break;
783 if (do_direct() && buffer[0] == '#') {
784 char *bp;
785
786 if (buffer[1] != '#')
787 break;
788 for (cp = (bp = buffer) + 1; *cp; cp++)
789 *bp++ = *cp;
790 *bp = '\0';
791 }
792 }
793
794 if (listsw)
795 ct->c_end = ftell (out);
796 fclose (out);
797
798 /* parse content type */
799 if (get_ctinfo (content, ct, inlineD) == NOTOK)
800 done (1);
801
802 for (s2i = str2cts; s2i->si_key; s2i++)
803 if (!strcasecmp (ci->ci_type, s2i->si_key))
804 break;
805 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
806 s2i++;
807
808 /*
809 * check type specified (possibly implicitly)
810 */
811 switch (ct->c_type = s2i->si_val) {
812 case CT_MESSAGE:
813 if (!strcasecmp (ci->ci_subtype, "rfc822")) {
814 ct->c_encoding = CE_7BIT;
815 goto call_init;
816 }
817 /* else fall... */
818 case CT_MULTIPART:
819 adios (NULL, "it doesn't make sense to define an in-line %s content",
820 ct->c_type == CT_MESSAGE ? "message" : "multipart");
821 /* NOTREACHED */
822
823 default:
824 call_init:
825 if ((ct->c_ctinitfnx = s2i->si_init))
826 (*ct->c_ctinitfnx) (ct);
827 break;
828 }
829
830 if (cp)
831 fseek (in, pos, SEEK_SET);
832 return OK;
833 }
834
835 /*
836 * If we've reached this point, the next line
837 * must be some type of explicit directive.
838 */
839
840 /* check if directive is external-type */
841 extrnal = (buf[1] == '@');
842
843 /* parse directive */
844 if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
845 done (1);
846
847 /* check directive against the list of MIME types */
848 for (s2i = str2cts; s2i->si_key; s2i++)
849 if (!strcasecmp (ci->ci_type, s2i->si_key))
850 break;
851
852 /*
853 * Check if the directive specified a valid type.
854 * This will happen if it was one of the following forms:
855 *
856 * #type/subtype (or)
857 * #@type/subtype
858 */
859 if (s2i->si_key) {
860 if (!ci->ci_subtype)
861 adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
862
863 switch (ct->c_type = s2i->si_val) {
864 case CT_MULTIPART:
865 adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
866 ci->ci_type, ci->ci_subtype);
867 /* NOTREACHED */
868
869 case CT_MESSAGE:
870 if (!strcasecmp (ci->ci_subtype, "partial"))
871 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
872 ci->ci_type, ci->ci_subtype);
873 if (!strcasecmp (ci->ci_subtype, "external-body"))
874 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
875 ci->ci_type, ci->ci_subtype);
876 use_forw:
877 adios (NULL,
878 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
879 ci->ci_type, ci->ci_subtype);
880 /* NOTREACHED */
881
882 default:
883 if ((ct->c_ctinitfnx = s2i->si_init))
884 (*ct->c_ctinitfnx) (ct);
885 break;
886 }
887
888 /*
889 * #@type/subtype (external types directive)
890 */
891 if (extrnal) {
892 struct exbody *e;
893 CT p;
894
895 if (!ci->ci_magic)
896 adios (NULL, "need external information for \"#@%s/%s\"",
897 ci->ci_type, ci->ci_subtype);
898 p = ct;
899
900 snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
901 free (ci->ci_magic);
902 ci->ci_magic = NULL;
903
904 /*
905 * Since we are using the current Content structure to
906 * hold information about the type of the external
907 * reference, we need to create another Content structure
908 * for the message/external-body to wrap it in.
909 */
910 if ((ct = (CT) mh_xcalloc (1, sizeof(*ct))) == NULL)
911 adios (NULL, "out of memory");
912 init_decoded_content(ct, infilename);
913 *ctp = ct;
914 if (get_ctinfo (buffer, ct, 0) == NOTOK)
915 done (1);
916 ct->c_type = CT_MESSAGE;
917 ct->c_subtype = MESSAGE_EXTERNAL;
918
919 if ((e = (struct exbody *) mh_xcalloc (1, sizeof(*e))) == NULL)
920 adios (NULL, "out of memory");
921 ct->c_ctparams = (void *) e;
922
923 e->eb_parent = ct;
924 e->eb_content = p;
925 p->c_ctexbody = e;
926
927 if (params_external (ct, 1) == NOTOK)
928 done (1);
929
930 return OK;
931 }
932
933 /* Handle [file] argument */
934 if (ci->ci_magic) {
935 /* check if specifies command to execute */
936 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
937 for (cp = ci->ci_magic + 1; isspace ((unsigned char) *cp); cp++)
938 continue;
939 if (!*cp)
940 adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
941 cp = add (cp, NULL);
942 free (ci->ci_magic);
943 ci->ci_magic = cp;
944 } else {
945 /* record filename of decoded contents */
946 ce->ce_file = ci->ci_magic;
947 if (access (ce->ce_file, R_OK) == NOTOK)
948 adios ("reading", "unable to access %s for", ce->ce_file);
949 if (listsw && stat (ce->ce_file, &st) != NOTOK)
950 ct->c_end = (long) st.st_size;
951 ci->ci_magic = NULL;
952 }
953 return OK;
954 }
955
956 /*
957 * No [file] argument, so check profile for
958 * method to compose content.
959 */
960 cp = context_find_by_type ("compose", ci->ci_type, ci->ci_subtype);
961 if (cp == NULL) {
962 content_error (NULL, ct, "don't know how to compose content");
963 done (1);
964 }
965 ci->ci_magic = add (cp, NULL);
966 return OK;
967 }
968
969 if (extrnal)
970 adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
971
972 /*
973 * Message directive
974 * #forw [+folder] [msgs]
975 */
976 if (!strcasecmp (ci->ci_type, "forw")) {
977 int msgnum;
978 char *folder, *arguments[MAXARGS];
979 struct msgs *mp;
980
981 if (ci->ci_magic) {
982 ap = brkstring (ci->ci_magic, " ", "\n");
983 copyip (ap, arguments, MAXARGS);
984 } else {
985 arguments[0] = "cur";
986 arguments[1] = NULL;
987 }
988 folder = NULL;
989
990 /* search the arguments for a folder name */
991 for (ap = arguments; *ap; ap++) {
992 cp = *ap;
993 if (*cp == '+' || *cp == '@') {
994 if (folder)
995 adios (NULL, "only one folder per #forw directive");
996 else
997 folder = pluspath (cp);
998 }
999 }
1000
1001 /* else, use the current folder */
1002 if (!folder)
1003 folder = add (getfolder (1), NULL);
1004
1005 if (!(mp = folder_read (folder, 0)))
1006 adios (NULL, "unable to read folder %s", folder);
1007 for (ap = arguments; *ap; ap++) {
1008 cp = *ap;
1009 if (*cp != '+' && *cp != '@')
1010 if (!m_convert (mp, cp))
1011 done (1);
1012 }
1013 free (folder);
1014 free_ctinfo (ct);
1015
1016 /*
1017 * If there is more than one message to include, make this
1018 * a content of type "multipart/digest" and insert each message
1019 * as a subpart. If there is only one message, then make this
1020 * a content of type "message/rfc822".
1021 */
1022 if (mp->numsel > 1) {
1023 /* we are forwarding multiple messages */
1024 if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
1025 done (1);
1026 ct->c_type = CT_MULTIPART;
1027 ct->c_subtype = MULTI_DIGEST;
1028
1029 if ((m = (struct multipart *) mh_xcalloc (1, sizeof(*m))) == NULL)
1030 adios (NULL, "out of memory");
1031 ct->c_ctparams = (void *) m;
1032 pp = &m->mp_parts;
1033
1034 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
1035 if (is_selected(mp, msgnum)) {
1036 struct part *part;
1037 CT p;
1038 CE pe;
1039
1040 if ((p = (CT) mh_xcalloc (1, sizeof(*p))) == NULL)
1041 adios (NULL, "out of memory");
1042 init_decoded_content (p, infilename);
1043 pe = &p->c_cefile;
1044 if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
1045 done (1);
1046 p->c_type = CT_MESSAGE;
1047 p->c_subtype = MESSAGE_RFC822;
1048
1049 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
1050 pe->ce_file = add (buffer, NULL);
1051 if (listsw && stat (pe->ce_file, &st) != NOTOK)
1052 p->c_end = (long) st.st_size;
1053
1054 if ((part = (struct part *) mh_xcalloc (1, sizeof(*part))) == NULL)
1055 adios (NULL, "out of memory");
1056 *pp = part;
1057 pp = &part->mp_next;
1058 part->mp_part = p;
1059 }
1060 }
1061 } else {
1062 /* we are forwarding one message */
1063 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
1064 done (1);
1065 ct->c_type = CT_MESSAGE;
1066 ct->c_subtype = MESSAGE_RFC822;
1067
1068 msgnum = mp->lowsel;
1069 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
1070 ce->ce_file = add (buffer, NULL);
1071 if (listsw && stat (ce->ce_file, &st) != NOTOK)
1072 ct->c_end = (long) st.st_size;
1073 }
1074
1075 folder_free (mp); /* free folder/message structure */
1076 return OK;
1077 }
1078
1079 /*
1080 * #end
1081 */
1082 if (!strcasecmp (ci->ci_type, "end")) {
1083 free_content (ct);
1084 *ctp = NULL;
1085 return DONE;
1086 }
1087
1088 /*
1089 * #begin [ alternative | parallel ]
1090 */
1091 if (!strcasecmp (ci->ci_type, "begin")) {
1092 if (!ci->ci_magic) {
1093 vrsn = MULTI_MIXED;
1094 cp = SubMultiPart[vrsn - 1].kv_key;
1095 } else if (!strcasecmp (ci->ci_magic, "alternative")) {
1096 vrsn = MULTI_ALTERNATE;
1097 cp = SubMultiPart[vrsn - 1].kv_key;
1098 } else if (!strcasecmp (ci->ci_magic, "parallel")) {
1099 vrsn = MULTI_PARALLEL;
1100 cp = SubMultiPart[vrsn - 1].kv_key;
1101 } else if (uprf (ci->ci_magic, "digest")) {
1102 goto use_forw;
1103 } else {
1104 vrsn = MULTI_UNKNOWN;
1105 cp = ci->ci_magic;
1106 }
1107
1108 free_ctinfo (ct);
1109 snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
1110 if (get_ctinfo (buffer, ct, 0) == NOTOK)
1111 done (1);
1112 ct->c_type = CT_MULTIPART;
1113 ct->c_subtype = vrsn;
1114
1115 if ((m = (struct multipart *) mh_xcalloc (1, sizeof(*m))) == NULL)
1116 adios (NULL, "out of memory");
1117 ct->c_ctparams = (void *) m;
1118
1119 pp = &m->mp_parts;
1120 while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
1121 struct part *part;
1122 CT p;
1123
1124 if (user_content (in, buffer, &p, infilename) == DONE) {
1125 if (!m->mp_parts)
1126 adios (NULL, "empty \"#begin ... #end\" sequence");
1127 return OK;
1128 }
1129 if (!p)
1130 continue;
1131
1132 if ((part = (struct part *) mh_xcalloc (1, sizeof(*part))) == NULL)
1133 adios (NULL, "out of memory");
1134 *pp = part;
1135 pp = &part->mp_next;
1136 part->mp_part = p;
1137 }
1138 admonish (NULL, "premature end-of-file, missing #end");
1139 return OK;
1140 }
1141
1142 /*
1143 * Unknown directive
1144 */
1145 adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
1146 return NOTOK; /* NOT REACHED */
1147 }
1148
1149
1150 static void
1151 set_id (CT ct, int top)
1152 {
1153 char contentid[BUFSIZ];
1154 static int partno;
1155 static time_t clock = 0;
1156 static char *msgfmt;
1157
1158 if (clock == 0) {
1159 time (&clock);
1160 snprintf (contentid, sizeof(contentid), "%s\n", message_id (clock, 1));
1161 partno = 0;
1162 msgfmt = getcpy(contentid);
1163 }
1164 snprintf (contentid, sizeof(contentid), msgfmt, top ? 0 : ++partno);
1165 ct->c_id = getcpy (contentid);
1166 }
1167
1168
1169 /*
1170 * Fill out, or expand the various contents in the composition
1171 * draft. Read-in any necessary files. Parse and execute any
1172 * commands specified by profile composition strings.
1173 */
1174
1175 static int
1176 compose_content (CT ct, int verbose)
1177 {
1178 CE ce = &ct->c_cefile;
1179
1180 switch (ct->c_type) {
1181 case CT_MULTIPART:
1182 {
1183 int partnum;
1184 char *pp;
1185 char partnam[BUFSIZ];
1186 struct multipart *m = (struct multipart *) ct->c_ctparams;
1187 struct part *part;
1188
1189 if (ct->c_partno) {
1190 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1191 pp = partnam + strlen (partnam);
1192 } else {
1193 pp = partnam;
1194 }
1195
1196 /* first, we call compose_content on all the subparts */
1197 for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
1198 CT p = part->mp_part;
1199
1200 sprintf (pp, "%d", partnum);
1201 p->c_partno = add (partnam, NULL);
1202 if (compose_content (p, verbose) == NOTOK)
1203 return NOTOK;
1204 }
1205
1206 /*
1207 * If the -rfc934mode switch is given, then check all
1208 * the subparts of a multipart/digest. If they are all
1209 * message/rfc822, then mark this content and all
1210 * subparts with the rfc934 compatibility mode flag.
1211 */
1212 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
1213 int is934 = 1;
1214
1215 for (part = m->mp_parts; part; part = part->mp_next) {
1216 CT p = part->mp_part;
1217
1218 if (p->c_subtype != MESSAGE_RFC822) {
1219 is934 = 0;
1220 break;
1221 }
1222 }
1223 ct->c_rfc934 = is934;
1224 for (part = m->mp_parts; part; part = part->mp_next) {
1225 CT p = part->mp_part;
1226
1227 if ((p->c_rfc934 = is934))
1228 p->c_end++;
1229 }
1230 }
1231
1232 if (listsw) {
1233 ct->c_end = (partnum = strlen (prefix) + 2) + 2;
1234 if (ct->c_rfc934)
1235 ct->c_end += 1;
1236
1237 for (part = m->mp_parts; part; part = part->mp_next)
1238 ct->c_end += part->mp_part->c_end + partnum;
1239 }
1240 }
1241 break;
1242
1243 case CT_MESSAGE:
1244 /* Nothing to do for type message */
1245 break;
1246
1247 /*
1248 * Discrete types (text/application/audio/image/video)
1249 */
1250 default:
1251 if (!ce->ce_file) {
1252 pid_t child_id;
1253 int i, xstdout, len, buflen;
1254 char *bp, *cp;
1255 char *vec[4], buffer[BUFSIZ];
1256 FILE *out;
1257 CI ci = &ct->c_ctinfo;
1258 char *tfile = NULL;
1259
1260 if (!(cp = ci->ci_magic))
1261 adios (NULL, "internal error(5)");
1262
1263 if ((tfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
1264 adios("mhbuildsbr", "unable to create temporary file in %s",
1265 get_temp_dir());
1266 }
1267 ce->ce_file = add (tfile, NULL);
1268 ce->ce_unlink = 1;
1269
1270 xstdout = 0;
1271
1272 /* Get buffer ready to go */
1273 bp = buffer;
1274 bp[0] = '\0';
1275 buflen = sizeof(buffer);
1276
1277 /*
1278 * Parse composition string into buffer
1279 */
1280 for ( ; *cp; cp++) {
1281 if (*cp == '%') {
1282 switch (*++cp) {
1283 case 'a':
1284 {
1285 /* insert parameters from directive */
1286 char *s = "";
1287 PM pm;
1288
1289 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1290 snprintf (bp, buflen, "%s%s=\"%s\"", s,
1291 pm->pm_name, get_param_value(pm, '?'));
1292 len = strlen (bp);
1293 bp += len;
1294 buflen -= len;
1295 s = " ";
1296 }
1297 }
1298 break;
1299
1300 case 'F':
1301 /* %f, and stdout is not-redirected */
1302 xstdout = 1;
1303 /* and fall... */
1304
1305 case 'f':
1306 /*
1307 * insert temporary filename where
1308 * content should be written
1309 */
1310 snprintf (bp, buflen, "%s", ce->ce_file);
1311 break;
1312
1313 case 's':
1314 /* insert content subtype */
1315 strncpy (bp, ci->ci_subtype, buflen);
1316 break;
1317
1318 case '%':
1319 /* insert character % */
1320 goto raw;
1321
1322 default:
1323 *bp++ = *--cp;
1324 *bp = '\0';
1325 buflen--;
1326 continue;
1327 }
1328 len = strlen (bp);
1329 bp += len;
1330 buflen -= len;
1331 } else {
1332 raw:
1333 *bp++ = *cp;
1334 *bp = '\0';
1335 buflen--;
1336 }
1337 }
1338
1339 if (verbose)
1340 printf ("composing content %s/%s from command\n\t%s\n",
1341 ci->ci_type, ci->ci_subtype, buffer);
1342
1343 fflush (stdout); /* not sure if need for -noverbose */
1344
1345 vec[0] = "/bin/sh";
1346 vec[1] = "-c";
1347 vec[2] = buffer;
1348 vec[3] = NULL;
1349
1350 if ((out = fopen (ce->ce_file, "w")) == NULL)
1351 adios (ce->ce_file, "unable to open for writing");
1352
1353 for (i = 0; (child_id = fork()) == NOTOK && i > 5; i++)
1354 sleep (5);
1355 switch (child_id) {
1356 case NOTOK:
1357 adios ("fork", "unable to fork");
1358 /* NOTREACHED */
1359
1360 case OK:
1361 if (!xstdout)
1362 dup2 (fileno (out), 1);
1363 close (fileno (out));
1364 execvp ("/bin/sh", vec);
1365 fprintf (stderr, "unable to exec ");
1366 perror ("/bin/sh");
1367 _exit (-1);
1368 /* NOTREACHED */
1369
1370 default:
1371 fclose (out);
1372 if (pidXwait(child_id, NULL))
1373 done (1);
1374 break;
1375 }
1376 }
1377
1378 /* Check size of file */
1379 if (listsw && ct->c_end == 0L) {
1380 struct stat st;
1381
1382 if (stat (ce->ce_file, &st) != NOTOK)
1383 ct->c_end = (long) st.st_size;
1384 }
1385 break;
1386 }
1387
1388 return OK;
1389 }
1390
1391
1392 /*
1393 * Scan the content.
1394 *
1395 * 1) choose a transfer encoding.
1396 * 2) check for clashes with multipart boundary string.
1397 * 3) for text content, figure out which character set is being used.
1398 *
1399 * If there is a clash with one of the contents and the multipart boundary,
1400 * this function will exit with NOTOK. This will cause the scanning process
1401 * to be repeated with a different multipart boundary. It is possible
1402 * (although highly unlikely) that this scan will be repeated multiple times.
1403 */
1404
1405 static int
1406 scan_content (CT ct, size_t maxunencoded)
1407 {
1408 int prefix_len;
1409 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1410 int checknul = 0, containsnul = 0; /* check if contains NULs */
1411 int checklinelen = 0, linelen = 0; /* check for long lines */
1412 int checkllinelen = 0; /* check for extra-long lines */
1413 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1414 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1415 char *cp = NULL;
1416 char *bufp = NULL;
1417 size_t buflen;
1418 ssize_t gotlen;
1419 struct text *t = NULL;
1420 FILE *in = NULL;
1421 CE ce = &ct->c_cefile;
1422
1423 /*
1424 * handle multipart by scanning all subparts
1425 * and then checking their encoding.
1426 */
1427 if (ct->c_type == CT_MULTIPART) {
1428 struct multipart *m = (struct multipart *) ct->c_ctparams;
1429 struct part *part;
1430
1431 /* initially mark the domain of enclosing multipart as 7bit */
1432 ct->c_encoding = CE_7BIT;
1433
1434 for (part = m->mp_parts; part; part = part->mp_next) {
1435 CT p = part->mp_part;
1436
1437 if (scan_content (p, maxunencoded) == NOTOK) /* choose encoding for subpart */
1438 return NOTOK;
1439
1440 /* if necessary, enlarge encoding for enclosing multipart */
1441 if (p->c_encoding == CE_BINARY)
1442 ct->c_encoding = CE_BINARY;
1443 if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
1444 ct->c_encoding = CE_8BIT;
1445 }
1446
1447 return OK;
1448 }
1449
1450 /*
1451 * Decide what to check while scanning this content. Note that
1452 * for text content we always check for 8bit characters if the
1453 * charset is unspecified, because that controls whether or not the
1454 * character set is us-ascii or retrieved from the locale.
1455 */
1456
1457 if (ct->c_type == CT_TEXT) {
1458 t = (struct text *) ct->c_ctparams;
1459 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1460 check8bit = 1;
1461 checknul = 1;
1462 }
1463 }
1464
1465 switch (ct->c_reqencoding) {
1466 case CE_8BIT:
1467 checkllinelen = 1;
1468 checkboundary = 1;
1469 break;
1470 case CE_QUOTED:
1471 checkboundary = 1;
1472 break;
1473 case CE_BASE64:
1474 break;
1475 case CE_UNKNOWN:
1476 /* Use the default rules based on content-type */
1477 switch (ct->c_type) {
1478 case CT_TEXT:
1479 checkboundary = 1;
1480 checklinelen = 1;
1481 if (ct->c_subtype == TEXT_PLAIN) {
1482 checklinespace = 0;
1483 } else {
1484 checklinespace = 1;
1485 }
1486 break;
1487
1488 case CT_APPLICATION:
1489 check8bit = 1;
1490 checknul = 1;
1491 checklinelen = 1;
1492 checklinespace = 1;
1493 checkboundary = 1;
1494 break;
1495
1496 case CT_MESSAGE:
1497 checklinelen = 0;
1498 checklinespace = 0;
1499
1500 /* don't check anything for message/external */
1501 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1502 checkboundary = 0;
1503 check8bit = 0;
1504 } else {
1505 checkboundary = 1;
1506 check8bit = 1;
1507 }
1508 break;
1509
1510 case CT_AUDIO:
1511 case CT_IMAGE:
1512 case CT_VIDEO:
1513 /*
1514 * Don't check anything for these types,
1515 * since we are forcing use of base64, unless
1516 * the content-type was specified by a mhbuild directive.
1517 */
1518 check8bit = 0;
1519 checklinelen = 0;
1520 checklinespace = 0;
1521 checkboundary = 0;
1522 break;
1523 }
1524 }
1525
1526 /*
1527 * Scan the unencoded content
1528 */
1529 if (check8bit || checklinelen || checklinespace || checkboundary ||
1530 checkllinelen || checknul) {
1531 if ((in = fopen (ce->ce_file, "r")) == NULL)
1532 adios (ce->ce_file, "unable to open for reading");
1533 prefix_len = strlen (prefix);
1534
1535 while ((gotlen = getline(&bufp, &buflen, in)) != -1) {
1536 /*
1537 * Check for 8bit and NUL data.
1538 */
1539 for (cp = bufp; (check8bit || checknul) &&
1540 cp < bufp + gotlen; cp++) {
1541 if (!isascii ((unsigned char) *cp)) {
1542 contains8bit = 1;
1543 check8bit = 0; /* no need to keep checking */
1544 }
1545 if (!*cp) {
1546 containsnul = 1;
1547 checknul = 0; /* no need to keep checking */
1548 }
1549 }
1550
1551 /*
1552 * Check line length.
1553 */
1554 if (checklinelen && ((size_t)gotlen > maxunencoded + 1)) {
1555 linelen = 1;
1556 checklinelen = 0; /* no need to keep checking */
1557 }
1558
1559 /*
1560 * RFC 5322 specifies that a message cannot contain a line
1561 * greater than 998 characters (excluding the CRLF). If we
1562 * get one of those lines and linelen is NOT set, then abort.
1563 */
1564
1565 if (checkllinelen && !linelen &&
1566 (gotlen > MAXLONGLINE + 1)) {
1567 adios(NULL, "Line in content exceeds maximum line limit (%d)",
1568 MAXLONGLINE);
1569 }
1570
1571 /*
1572 * Check if line ends with a space.
1573 */
1574 if (checklinespace && (cp = bufp + gotlen - 2) > bufp &&
1575 isspace ((unsigned char) *cp)) {
1576 linespace = 1;
1577 checklinespace = 0; /* no need to keep checking */
1578 }
1579
1580 /*
1581 * Check if content contains a line that clashes
1582 * with our standard boundary for multipart messages.
1583 */
1584 if (checkboundary && bufp[0] == '-' && bufp[1] == '-') {
1585 for (cp = bufp + gotlen - 1; cp >= bufp; cp--)
1586 if (!isspace ((unsigned char) *cp))
1587 break;
1588 *++cp = '\0';
1589 if (!strncmp(bufp + 2, prefix, prefix_len) &&
1590 isdigit((unsigned char) bufp[2 + prefix_len])) {
1591 boundaryclash = 1;
1592 checkboundary = 0; /* no need to keep checking */
1593 }
1594 }
1595 }
1596 fclose (in);
1597 free(bufp);
1598 }
1599
1600 /*
1601 * If the content is text and didn't specify a character set,
1602 * we need to figure out which one was used.
1603 */
1604 set_charset (ct, contains8bit);
1605
1606 /*
1607 * Decide which transfer encoding to use.
1608 */
1609
1610 if (ct->c_reqencoding != CE_UNKNOWN)
1611 ct->c_encoding = ct->c_reqencoding;
1612 else {
1613 int wants_q_p = (containsnul || linelen || linespace || checksw);
1614
1615 switch (ct->c_type) {
1616 case CT_TEXT:
1617 if (wants_q_p)
1618 ct->c_encoding = CE_QUOTED;
1619 else if (contains8bit)
1620 ct->c_encoding = CE_8BIT;
1621 else
1622 ct->c_encoding = CE_7BIT;
1623
1624 break;
1625
1626 case CT_APPLICATION:
1627 /* For application type, use base64, except when postscript */
1628 if (wants_q_p || contains8bit) {
1629 if (ct->c_subtype == APPLICATION_POSTSCRIPT)
1630 ct->c_encoding = CE_QUOTED; /* historical */
1631 else
1632 ct->c_encoding = CE_BASE64;
1633 } else {
1634 ct->c_encoding = CE_7BIT;
1635 }
1636 break;
1637
1638 case CT_MESSAGE:
1639 ct->c_encoding = contains8bit ? CE_8BIT : CE_7BIT;
1640 break;
1641
1642 case CT_AUDIO:
1643 case CT_IMAGE:
1644 case CT_VIDEO:
1645 /* For audio, image, and video contents, just use base64 */
1646 ct->c_encoding = CE_BASE64;
1647 break;
1648 }
1649 }
1650
1651 return (boundaryclash ? NOTOK : OK);
1652 }
1653
1654
1655 /*
1656 * Scan the content structures, and build header
1657 * fields that will need to be output into the
1658 * message.
1659 */
1660
1661 static int
1662 build_headers (CT ct, int header_encoding)
1663 {
1664 int cc, mailbody, extbody, len;
1665 char *np, *vp, buffer[BUFSIZ];
1666 CI ci = &ct->c_ctinfo;
1667
1668 /*
1669 * If message is type multipart, then add the multipart
1670 * boundary to the list of attribute/value pairs.
1671 */
1672 if (ct->c_type == CT_MULTIPART) {
1673 static int level = 0; /* store nesting level */
1674
1675 snprintf (buffer, sizeof(buffer), "%s%d", prefix, level++);
1676 add_param(&ci->ci_first_pm, &ci->ci_last_pm, "boundary", buffer, 0);
1677 }
1678
1679 /*
1680 * Skip the output of Content-Type, parameters, content
1681 * description and disposition, and Content-ID if the
1682 * content is of type "message" and the rfc934 compatibility
1683 * flag is set (which means we are inside multipart/digest
1684 * and the switch -rfc934mode was given).
1685 */
1686 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1687 goto skip_headers;
1688
1689 /*
1690 * output the content type and subtype
1691 */
1692 np = add (TYPE_FIELD, NULL);
1693 vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1694
1695 /* keep track of length of line */
1696 len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
1697 + strlen (ci->ci_subtype) + 3;
1698
1699 extbody = ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_EXTERNAL;
1700 mailbody = extbody && ((struct exbody *) ct->c_ctparams)->eb_body;
1701
1702 /*
1703 * Append the attribute/value pairs to
1704 * the end of the Content-Type line.
1705 */
1706
1707 if (ci->ci_first_pm) {
1708 char *s = output_params(len, ci->ci_first_pm, &len, mailbody);
1709
1710 if (!s)
1711 adios(NULL, "Internal error: failed outputting Content-Type "
1712 "parameters");
1713
1714 vp = add (s, vp);
1715 free(s);
1716 }
1717
1718 /*
1719 * Append any RFC-822 comment to the end of
1720 * the Content-Type line.
1721 */
1722 if (ci->ci_comment) {
1723 snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1724 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
1725 vp = add ("\n\t", vp);
1726 len = 8;
1727 } else {
1728 vp = add (" ", vp);
1729 len++;
1730 }
1731 vp = add (buffer, vp);
1732 len += cc;
1733 }
1734 vp = add ("\n", vp);
1735 add_header (ct, np, vp);
1736
1737 /*
1738 * output the Content-ID, unless disabled by -nocontentid
1739 */
1740 if (contentidsw && ct->c_id) {
1741 np = add (ID_FIELD, NULL);
1742 vp = concat (" ", ct->c_id, NULL);
1743 add_header (ct, np, vp);
1744 }
1745 /*
1746 * output the Content-Description
1747 */
1748 if (ct->c_descr) {
1749 np = add (DESCR_FIELD, NULL);
1750 vp = concat (" ", ct->c_descr, NULL);
1751 if (encode_rfc2047(DESCR_FIELD, &vp, header_encoding, NULL))
1752 adios(NULL, "Unable to encode %s header", DESCR_FIELD);
1753 add_header (ct, np, vp);
1754 }
1755
1756 /*
1757 * output the Content-Disposition. If it's NULL but c_dispo_type is
1758 * set, then we need to build it.
1759 */
1760 if (ct->c_dispo) {
1761 np = add (DISPO_FIELD, NULL);
1762 vp = concat (" ", ct->c_dispo, NULL);
1763 add_header (ct, np, vp);
1764 } else if (ct->c_dispo_type) {
1765 vp = concat (" ", ct->c_dispo_type, NULL);
1766 len = strlen(DISPO_FIELD) + strlen(vp) + 1;
1767 np = output_params(len, ct->c_dispo_first, NULL, 0);
1768 vp = add(np, vp);
1769 vp = add("\n", vp);
1770 if (np)
1771 free(np);
1772 add_header (ct, getcpy(DISPO_FIELD), vp);
1773 }
1774
1775 skip_headers:
1776 /*
1777 * If this is the internal content structure for a
1778 * "message/external", then we are done with the
1779 * headers (since it has no body).
1780 */
1781 if (ct->c_ctexbody)
1782 return OK;
1783
1784 /*
1785 * output the Content-MD5
1786 */
1787 if (checksw) {
1788 np = add (MD5_FIELD, NULL);
1789 vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
1790 add_header (ct, np, vp);
1791 }
1792
1793 /*
1794 * output the Content-Transfer-Encoding
1795 */
1796 switch (ct->c_encoding) {
1797 case CE_7BIT:
1798 /* Nothing to output */
1799 break;
1800
1801 case CE_8BIT:
1802 np = add (ENCODING_FIELD, NULL);
1803 vp = concat (" ", "8bit", "\n", NULL);
1804 add_header (ct, np, vp);
1805 break;
1806
1807 case CE_QUOTED:
1808 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1809 adios (NULL, "internal error, invalid encoding");
1810
1811 np = add (ENCODING_FIELD, NULL);
1812 vp = concat (" ", "quoted-printable", "\n", NULL);
1813 add_header (ct, np, vp);
1814 break;
1815
1816 case CE_BASE64:
1817 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1818 adios (NULL, "internal error, invalid encoding");
1819
1820 np = add (ENCODING_FIELD, NULL);
1821 vp = concat (" ", "base64", "\n", NULL);
1822 add_header (ct, np, vp);
1823 break;
1824
1825 case CE_BINARY:
1826 if (ct->c_type == CT_MESSAGE)
1827 adios (NULL, "internal error, invalid encoding");
1828
1829 np = add (ENCODING_FIELD, NULL);
1830 vp = concat (" ", "binary", "\n", NULL);
1831 add_header (ct, np, vp);
1832 break;
1833
1834 default:
1835 adios (NULL, "unknown transfer encoding in content");
1836 break;
1837 }
1838
1839 /*
1840 * Additional content specific header processing
1841 */
1842 switch (ct->c_type) {
1843 case CT_MULTIPART:
1844 {
1845 struct multipart *m;
1846 struct part *part;
1847
1848 m = (struct multipart *) ct->c_ctparams;
1849 for (part = m->mp_parts; part; part = part->mp_next) {
1850 CT p;
1851
1852 p = part->mp_part;
1853 build_headers (p, header_encoding);
1854 }
1855 }
1856 break;
1857
1858 case CT_MESSAGE:
1859 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1860 struct exbody *e;
1861
1862 e = (struct exbody *) ct->c_ctparams;
1863 build_headers (e->eb_content, header_encoding);
1864 }
1865 break;
1866
1867 default:
1868 /* Nothing to do */
1869 break;
1870 }
1871
1872 return OK;
1873 }
1874
1875
1876 static char nib2b64[0x40+1] =
1877 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1878
1879 static char *
1880 calculate_digest (CT ct, int asciiP)
1881 {
1882 int cc;
1883 char *vp, *op;
1884 unsigned char *dp;
1885 unsigned char digest[16];
1886 unsigned char outbuf[25];
1887 MD5_CTX mdContext;
1888 CE ce = &ct->c_cefile;
1889 char *infilename = ce->ce_file ? ce->ce_file : ct->c_file;
1890 FILE *in;
1891
1892 /* open content */
1893 if ((in = fopen (infilename, "r")) == NULL)
1894 adios (infilename, "unable to open for reading");
1895
1896 /* Initialize md5 context */
1897 MD5Init (&mdContext);
1898
1899 /* calculate md5 message digest */
1900 if (asciiP) {
1901 char *bufp = NULL;
1902 size_t buflen;
1903 ssize_t gotlen;
1904 while ((gotlen = getline(&bufp, &buflen, in)) != -1) {
1905 char c, *cp;
1906
1907 cp = bufp + gotlen - 1;
1908 if ((c = *cp) == '\n')
1909 gotlen--;
1910
1911 MD5Update (&mdContext, (unsigned char *) bufp,
1912 (unsigned int) gotlen);
1913
1914 if (c == '\n')
1915 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
1916 }
1917 } else {
1918 char buffer[BUFSIZ];
1919 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
1920 MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
1921 }
1922
1923 /* md5 finalization. Write digest and zero md5 context */
1924 MD5Final (digest, &mdContext);
1925
1926 /* close content */
1927 fclose (in);
1928
1929 /* print debugging info */
1930 if (debugsw) {
1931 unsigned char *ep;
1932
1933 fprintf (stderr, "MD5 digest=");
1934 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1935 dp < ep; dp++)
1936 fprintf (stderr, "%02x", *dp & 0xff);
1937 fprintf (stderr, "\n");
1938 }
1939
1940 /* encode the digest using base64 */
1941 for (dp = digest, op = (char *) outbuf,
1942 cc = sizeof(digest) / sizeof(digest[0]);
1943 cc > 0; cc -= 3, op += 4) {
1944 unsigned long bits;
1945 char *bp;
1946
1947 bits = (*dp++ & 0xff) << 16;
1948 if (cc > 1) {
1949 bits |= (*dp++ & 0xff) << 8;
1950 if (cc > 2)
1951 bits |= *dp++ & 0xff;
1952 }
1953
1954 for (bp = op + 4; bp > op; bits >>= 6)
1955 *--bp = nib2b64[bits & 0x3f];
1956 if (cc < 3) {
1957 *(op + 3) = '=';
1958 if (cc < 2)
1959 *(op + 2) = '=';
1960 }
1961 }
1962
1963 /* null terminate string */
1964 outbuf[24] = '\0';
1965
1966 /* now make copy and return string */
1967 vp = concat (" ", outbuf, "\n", NULL);
1968 return vp;
1969 }
1970
1971 /*
1972 * Set things up for the content structure for file "filename" that
1973 * we want to attach
1974 */
1975
1976 static void
1977 setup_attach_content(CT ct, char *filename)
1978 {
1979 char *type, *simplename = r1bindex(filename, '/');
1980 struct str2init *s2i;
1981 PM pm;
1982
1983 if (! (type = mime_type(filename))) {
1984 adios(NULL, "Unable to determine MIME type of \"%s\"", filename);
1985 }
1986
1987 /*
1988 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1989 * since we're just feeding it a MIME type we have to add those ourself.
1990 * Map that to a valid content-type label and call any initialization
1991 * function.
1992 */
1993
1994 if (get_ctinfo(type, ct, 0) == NOTOK)
1995 done(1);
1996
1997 free(type);
1998
1999 for (s2i = str2cts; s2i->si_key; s2i++)
2000 if (strcasecmp(ct->c_ctinfo.ci_type, s2i->si_key) == 0)
2001 break;
2002 if (!s2i->si_key && !uprf(ct->c_ctinfo.ci_type, "X-"))
2003 s2i++;
2004
2005 /*
2006 * Make sure the type isn't incompatible with what we can handle
2007 */
2008
2009 switch (ct->c_type = s2i->si_val) {
2010 case CT_MULTIPART:
2011 adios (NULL, "multipart types must be specified by mhbuild directives");
2012 /* NOTREACHED */
2013
2014 case CT_MESSAGE:
2015 if (strcasecmp(ct->c_ctinfo.ci_subtype, "partial") == 0)
2016 adios(NULL, "Sorry, %s/%s isn't supported", ct->c_ctinfo.ci_type,
2017 ct->c_ctinfo.ci_subtype);
2018 if (strcasecmp(ct->c_ctinfo.ci_subtype, "external-body") == 0)
2019 adios(NULL, "external-body messages must be specified "
2020 "by mhbuild directives");
2021 /* Fall through */
2022
2023 default:
2024 /*
2025 * This sets the subtype, if it's significant
2026 */
2027 if ((ct->c_ctinitfnx = s2i->si_init))
2028 (*ct->c_ctinitfnx)(ct);
2029 break;
2030 }
2031
2032 /*
2033 * Feed in a few attributes; specifically, the name attribute, the
2034 * content-description, and the content-disposition.
2035 */
2036
2037 for (pm = ct->c_ctinfo.ci_first_pm; pm; pm = pm->pm_next) {
2038 if (strcasecmp(pm->pm_name, "name") == 0) {
2039 if (pm->pm_value)
2040 free(pm->pm_value);
2041 pm->pm_value = getcpy(simplename);
2042 break;
2043 }
2044 }
2045
2046 if (pm == NULL)
2047 add_param(&ct->c_ctinfo.ci_first_pm, &ct->c_ctinfo.ci_last_pm,
2048 "name", simplename, 0);
2049
2050 ct->c_descr = getcpy(simplename);
2051 ct->c_descr = add("\n", ct->c_descr);
2052 ct->c_cefile.ce_file = getcpy(filename);
2053
2054 set_disposition (ct);
2055
2056 add_param(&ct->c_dispo_first, &ct->c_dispo_last, "filename", simplename, 0);
2057 }
2058
2059 /*
2060 * If disposition type hasn't already been set in ct:
2061 * Look for mhbuild-disposition-<type>/<subtype> entry
2062 * that specifies Content-Disposition type. Only
2063 * 'attachment' and 'inline' are allowed. Default to
2064 * 'attachment'.
2065 */
2066 void
2067 set_disposition (CT ct) {
2068 if (ct->c_dispo_type == NULL) {
2069 char *cp = context_find_by_type ("disposition", ct->c_ctinfo.ci_type,
2070 ct->c_ctinfo.ci_subtype);
2071
2072 if (cp && strcasecmp (cp, "attachment") &&
2073 strcasecmp (cp, "inline")) {
2074 admonish (NULL, "configuration problem: %s-disposition-%s%s%s "
2075 "specifies '%s' but only 'attachment' and 'inline' are "
2076 "allowed", invo_name,
2077 ct->c_ctinfo.ci_type,
2078 ct->c_ctinfo.ci_subtype ? "/" : "",
2079 ct->c_ctinfo.ci_subtype ? ct->c_ctinfo.ci_subtype : "",
2080 cp);
2081 }
2082
2083 ct->c_dispo_type = cp ? getcpy (cp) : getcpy ("attachment");
2084 }
2085 }
2086
2087 /*
2088 * Set text content charset if it was unspecified. contains8bit
2089 * selctions:
2090 * 0: content does not contain 8-bit characters
2091 * 1: content contains 8-bit characters
2092 * -1: ignore content and use user's locale to determine charset
2093 */
2094 void
2095 set_charset (CT ct, int contains8bit) {
2096 if (ct->c_type == CT_TEXT) {
2097 struct text *t;
2098
2099 if (ct->c_ctparams == NULL) {
2100 if ((t = ct->c_ctparams =
2101 (struct text *) mh_xcalloc (1, sizeof (struct text))) ==
2102 NULL) {
2103 adios (NULL, "out of memory");
2104 }
2105 t->tx_charset = CHARSET_UNSPECIFIED;
2106 } else {
2107 t = (struct text *) ct->c_ctparams;
2108 }
2109
2110 if (t->tx_charset == CHARSET_UNSPECIFIED) {
2111 CI ci = &ct->c_ctinfo;
2112 char *eightbitcharset = write_charset_8bit();
2113 char *charset = contains8bit ? eightbitcharset : "us-ascii";
2114
2115 if (contains8bit == 1 &&
2116 strcasecmp (eightbitcharset, "US-ASCII") == 0) {
2117 adios (NULL, "Text content contains 8 bit characters, but "
2118 "character set is US-ASCII");
2119 }
2120
2121 add_param (&ci->ci_first_pm, &ci->ci_last_pm, "charset", charset,
2122 0);
2123
2124 t->tx_charset = CHARSET_SPECIFIED;
2125 }
2126 }
2127 }
2128
2129
2130 /*
2131 * Look at all of the replied-to message parts and expand any that
2132 * are matched by a pseudoheader. Except don't descend into
2133 * message parts.
2134 */
2135 void
2136 expand_pseudoheaders (CT ct, struct multipart *m, const char *infile,
2137 const convert_list *convert_head) {
2138 /* text_plain_ct is used to concatenate all of the text/plain
2139 replies into one part, instead of having each one in a separate
2140 part. */
2141 CT text_plain_ct = NULL;
2142
2143 switch (ct->c_type) {
2144 case CT_MULTIPART: {
2145 struct multipart *mp = (struct multipart *) ct->c_ctparams;
2146 struct part *part;
2147
2148 if (ct->c_subtype == MULTI_ALTERNATE) {
2149 int matched = 0;
2150
2151 /* The parts are in descending priority order (defined by
2152 RFC 2046 Sec. 5.1.4) because they were reversed by
2153 parse_mime (). So, stop looking for matches with
2154 immediate subparts after the first match of an
2155 alternative. */
2156 for (part = mp->mp_parts; ! matched && part; part = part->mp_next) {
2157 char *type_subtype =
2158 concat (part->mp_part->c_ctinfo.ci_type, "/",
2159 part->mp_part->c_ctinfo.ci_subtype, NULL);
2160
2161 if (part->mp_part->c_type == CT_MULTIPART) {
2162 expand_pseudoheaders (part->mp_part, m, infile,
2163 convert_head);
2164 } else {
2165 const convert_list *c;
2166
2167 for (c = convert_head; c; c = c->next) {
2168 if (! strcasecmp (type_subtype, c->type)) {
2169 expand_pseudoheader (part->mp_part, &text_plain_ct,
2170 m, infile,
2171 c->type, c->argstring);
2172 matched = 1;
2173 break;
2174 }
2175 }
2176 }
2177 free (type_subtype);
2178 }
2179 } else {
2180 for (part = mp->mp_parts; part; part = part->mp_next) {
2181 expand_pseudoheaders (part->mp_part, m, infile, convert_head);
2182 }
2183 }
2184 break;
2185 }
2186
2187 default: {
2188 char *type_subtype =
2189 concat (ct->c_ctinfo.ci_type, "/", ct->c_ctinfo.ci_subtype,
2190 NULL);
2191 const convert_list *c;
2192
2193 for (c = convert_head; c; c = c->next) {
2194 if (! strcasecmp (type_subtype, c->type)) {
2195 expand_pseudoheader (ct, &text_plain_ct, m, infile, c->type,
2196 c->argstring);
2197 break;
2198 }
2199 }
2200 free (type_subtype);
2201 break;
2202 }
2203 }
2204 }
2205
2206
2207 /*
2208 * Expand a single pseudoheader. It's for the specified type.
2209 */
2210 void
2211 expand_pseudoheader (CT ct, CT *text_plain_ct, struct multipart *m,
2212 const char *infile, const char *type,
2213 const char *argstring) {
2214 char *reply_file;
2215 FILE *reply_fp = NULL;
2216 char *convert, *type_p, *subtype_p;
2217 char *convert_command;
2218 char *charset = NULL;
2219 char *cp;
2220 struct str2init *s2i;
2221 CT reply_ct;
2222 struct part *part;
2223 int status;
2224
2225 type_p = getcpy (type);
2226 if ((subtype_p = strchr (type_p, '/'))) {
2227 *subtype_p++ = '\0';
2228 convert = context_find_by_type ("convert", type_p, subtype_p);
2229 } else {
2230 free (type_p);
2231 type_p = concat ("mhbuild-convert-", type, NULL);
2232 convert = context_find (type_p);
2233 }
2234 free (type_p);
2235
2236 if (! (convert)) {
2237 /* No mhbuild-convert- entry in mhn.defaults or profile
2238 for type. */
2239 return;
2240 }
2241 /* reply_file is used to pass the output of the convert. */
2242 reply_file = getcpy (m_mktemp2 (NULL, invo_name, NULL, NULL));
2243 convert_command =
2244 concat (convert, " ", argstring ? argstring : "", " >", reply_file,
2245 NULL);
2246
2247 /* Convert here . . . */
2248 ct->c_storeproc = getcpy (convert_command);
2249 ct->c_umask = ~m_gmprot ();
2250
2251 if ((status = show_content_aux (ct, 0, convert_command, NULL, NULL)) !=
2252 OK) {
2253 admonish (NULL, "store of %s content failed", type);
2254 }
2255 free (convert_command);
2256
2257 /* Fill out the the new ct, reply_ct. */
2258 reply_ct = (CT) mh_xcalloc (1, sizeof *reply_ct);
2259 init_decoded_content (reply_ct, infile);
2260
2261 if (extract_headers (reply_ct, reply_file, &reply_fp) == NOTOK) {
2262 free (reply_file);
2263 admonish (NULL,
2264 "failed to extract headers from convert output in %s",
2265 reply_file);
2266 return;
2267 }
2268
2269 /* This sets reply_ct->c_ctparams, and reply_ct->c_termproc if the
2270 charset can't be handled natively. */
2271 for (s2i = str2cts; s2i->si_key; s2i++) {
2272 if (strcasecmp(reply_ct->c_ctinfo.ci_type, s2i->si_key) == 0) {
2273 break;
2274 }
2275 }
2276
2277 if ((reply_ct->c_ctinitfnx = s2i->si_init)) {
2278 (*reply_ct->c_ctinitfnx)(reply_ct);
2279 }
2280
2281 if ((cp =
2282 get_param (reply_ct->c_ctinfo.ci_first_pm, "charset", '?', 1))) {
2283 /* The reply Content-Type had the charset. */
2284 charset = cp;
2285 } else {
2286 set_charset (reply_ct, -1);
2287 charset = get_param (reply_ct->c_ctinfo.ci_first_pm, "charset", '?', 1);
2288 if (reply_ct->c_reqencoding == CE_UNKNOWN) {
2289 /* Assume that 8bit is sufficient (for text). */
2290 reply_ct->c_reqencoding =
2291 strcasecmp (charset, "US-ASCII") ? CE_8BIT : CE_7BIT;
2292 }
2293 }
2294
2295 /* Concatenate text/plain parts. */
2296 if (reply_ct->c_type == CT_TEXT &&
2297 reply_ct->c_subtype == TEXT_PLAIN) {
2298 if (! *text_plain_ct && m->mp_parts && m->mp_parts->mp_part &&
2299 m->mp_parts->mp_part->c_type == CT_TEXT &&
2300 m->mp_parts->mp_part->c_subtype == TEXT_PLAIN) {
2301 *text_plain_ct = m->mp_parts->mp_part;
2302 /* Make sure that the charset is set in the text/plain
2303 part. */
2304 set_charset (*text_plain_ct, -1);
2305 if ((*text_plain_ct)->c_reqencoding == CE_UNKNOWN) {
2306 /* Assume that 8bit is sufficient (for text). */
2307 (*text_plain_ct)->c_reqencoding =
2308 strcasecmp (charset, "US-ASCII") ? CE_8BIT : CE_7BIT;
2309 }
2310 }
2311
2312 if (*text_plain_ct) {
2313 /* Only concatenate if the charsets are identical. */
2314 char *text_plain_ct_charset =
2315 get_param ((*text_plain_ct)->c_ctinfo.ci_first_pm, "charset",
2316 '?', 1);
2317
2318 if (strcasecmp (text_plain_ct_charset, charset) == 0) {
2319 /* Append this text/plain reply to the first one.
2320 If there's a problem anywhere along the way,
2321 instead attach it is a separate part. */
2322 int text_plain_reply =
2323 open ((*text_plain_ct)->c_cefile.ce_file,
2324 O_WRONLY | O_APPEND);
2325 int addl_reply = open (reply_file, O_RDONLY);
2326
2327 if (text_plain_reply != NOTOK && addl_reply != NOTOK) {
2328 /* Insert blank line before each addl part. */
2329 /* It would be nice not to do this for the first one. */
2330 if (write (text_plain_reply, "\n", 1) == 1) {
2331 /* Copy the text from the new reply and
2332 then free its Content struct. */
2333 cpydata (addl_reply, text_plain_reply,
2334 (*text_plain_ct)->c_cefile.ce_file,
2335 reply_file);
2336 if (close (text_plain_reply) == OK &&
2337 close (addl_reply) == OK) {
2338 if (reply_fp) { fclose (reply_fp); }
2339 free (reply_file);
2340 free_content (reply_ct);
2341 return;
2342 }
2343 }
2344 }
2345 }
2346 } else {
2347 *text_plain_ct = reply_ct;
2348 }
2349 }
2350
2351 reply_ct->c_cefile.ce_file = reply_file;
2352 reply_ct->c_cefile.ce_fp = reply_fp;
2353 reply_ct->c_cefile.ce_unlink = 1;
2354
2355 /* Attach the new part to the parent mulitpart/mixed, "m". */
2356 part = (struct part *) mh_xcalloc (1, sizeof *part);
2357 part->mp_part = reply_ct;
2358 if (m->mp_parts) {
2359 struct part *p;
2360
2361 for (p = m->mp_parts; p && p->mp_next; p = p->mp_next) { continue; }
2362 p->mp_next = part;
2363 } else {
2364 m->mp_parts = part;
2365 }
2366 }
2367
2368
2369 /* Extract any Content-Type header from beginning of convert output. */
2370 int
2371 extract_headers (CT ct, char *reply_file, FILE **reply_fp) {
2372 char *buffer = NULL, *cp, *end_of_header;
2373 int found_header = 0;
2374 struct stat statbuf;
2375
2376 /* Read the convert reply from the file to memory. */
2377 if (stat (reply_file, &statbuf) == NOTOK) {
2378 admonish (reply_file, "failed to stat");
2379 goto failed_to_extract_ct;
2380 }
2381
2382 buffer = mh_xmalloc (statbuf.st_size + 1);
2383
2384 if ((*reply_fp = fopen (reply_file, "r+")) == NULL ||
2385 fread (buffer, 1, (size_t) statbuf.st_size, *reply_fp) <
2386 (size_t) statbuf.st_size) {
2387 admonish (reply_file, "failed to read");
2388 goto failed_to_extract_ct;
2389 }
2390 buffer[statbuf.st_size] = '\0';
2391
2392 /* Look for a header in the convert reply. */
2393 if (strncasecmp (buffer, TYPE_FIELD, strlen (TYPE_FIELD)) == 0 &&
2394 buffer[strlen (TYPE_FIELD)] == ':') {
2395 if ((end_of_header = strstr (buffer, "\r\n\r\n"))) {
2396 end_of_header += 2;
2397 found_header = 1;
2398 } else if ((end_of_header = strstr (buffer, "\n\n"))) {
2399 ++end_of_header;
2400 found_header = 1;
2401 }
2402 }
2403
2404 if (found_header) {
2405 CT tmp_ct;
2406 char *tmp_file;
2407 FILE *tmp_f;
2408 size_t n;
2409
2410 /* Truncate buffer to just the C-T. */
2411 *end_of_header = '\0';
2412 n = strlen (buffer);
2413
2414 if (get_ctinfo (buffer + 14, ct, 0) != OK) {
2415 admonish (NULL, "unable to get content info for reply");
2416 goto failed_to_extract_ct;
2417 }
2418
2419 /* Hack. Use parse_mime() to detect the type/subtype of the
2420 reply, which we'll use below. */
2421 tmp_file = getcpy (m_mktemp2 (NULL, invo_name, NULL, NULL));
2422 if ((tmp_f = fopen (tmp_file, "w")) &&
2423 fwrite (buffer, 1, n, tmp_f) == n) {
2424 fclose (tmp_f);
2425 } else {
2426 goto failed_to_extract_ct;
2427 }
2428 tmp_ct = parse_mime (tmp_file);
2429
2430 if (tmp_ct) {
2431 /* The type and subtype were detected from the reply
2432 using parse_mime() above. */
2433 ct->c_type = tmp_ct->c_type;
2434 ct->c_subtype = tmp_ct->c_subtype;
2435 free_content (tmp_ct);
2436 }
2437
2438 free (tmp_file);
2439
2440 /* Rewrite the content without the header. */
2441 cp = end_of_header + 1;
2442 rewind (*reply_fp);
2443
2444 if (fwrite (cp, 1, statbuf.st_size - (cp - buffer), *reply_fp) <
2445 (size_t) (statbuf.st_size - (cp - buffer))) {
2446 admonish (reply_file, "failed to write");
2447 goto failed_to_extract_ct;
2448 }
2449
2450 if (ftruncate (fileno (*reply_fp), statbuf.st_size - (cp - buffer)) !=
2451 0) {
2452 advise (reply_file, "ftruncate");
2453 goto failed_to_extract_ct;
2454 }
2455 } else {
2456 /* No header section, assume the reply is text/plain. */
2457 ct->c_type = CT_TEXT;
2458 ct->c_subtype = TEXT_PLAIN;
2459 if (get_ctinfo ("text/plain", ct, 0) == NOTOK) {
2460 /* This never should fail, but just in case. */
2461 adios (NULL, "unable to get content info for reply");
2462 }
2463 }
2464
2465 /* free_encoding() will close reply_fp, which is passed through
2466 ct->c_cefile.ce_fp. */
2467 free (buffer);
2468 return OK;
2469
2470 failed_to_extract_ct:
2471 if (*reply_fp) { fclose (*reply_fp); }
2472 free (buffer);
2473 return NOTOK;
2474 }