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