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