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