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