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