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