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