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