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