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