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