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