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