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