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