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