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