]> diplodocus.org Git - nmh/blob - uip/mhbuildsbr.c
Reverted commit 9a4b4a3d3b27fe4a7ff6d0b8724ce1c06b5917eb.
[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 /* fall through */
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 /* else fall... */
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 /* and fall... */
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
1729 */
1730 if (contentidsw && ct->c_id) {
1731 np = mh_xstrdup(ID_FIELD);
1732 vp = concat (" ", ct->c_id, NULL);
1733 add_header (ct, np, vp);
1734 }
1735 /*
1736 * output the Content-Description
1737 */
1738 if (ct->c_descr) {
1739 np = mh_xstrdup(DESCR_FIELD);
1740 vp = concat (" ", ct->c_descr, NULL);
1741 if (header_encoding != CE_8BIT) {
1742 if (encode_rfc2047(DESCR_FIELD, &vp, header_encoding, NULL)) {
1743 adios(NULL, "Unable to encode %s header", DESCR_FIELD);
1744 }
1745 }
1746 add_header (ct, np, vp);
1747 }
1748
1749 /*
1750 * output the Content-Disposition. If it's NULL but c_dispo_type is
1751 * set, then we need to build it.
1752 */
1753 if (ct->c_dispo) {
1754 np = mh_xstrdup(DISPO_FIELD);
1755 vp = concat (" ", ct->c_dispo, NULL);
1756 add_header (ct, np, vp);
1757 } else if (ct->c_dispo_type) {
1758 vp = concat (" ", ct->c_dispo_type, NULL);
1759 len = strlen(DISPO_FIELD) + strlen(vp) + 1;
1760 np = output_params(len, ct->c_dispo_first, NULL, 0);
1761 vp = add(np, vp);
1762 vp = add("\n", vp);
1763 mh_xfree(np);
1764 add_header (ct, mh_xstrdup(DISPO_FIELD), vp);
1765 }
1766
1767 skip_headers:
1768 /*
1769 * If this is the internal content structure for a
1770 * "message/external", then we are done with the
1771 * headers (since it has no body).
1772 */
1773 if (ct->c_ctexbody)
1774 return OK;
1775
1776 /*
1777 * output the Content-MD5
1778 */
1779 if (checksw) {
1780 np = mh_xstrdup(MD5_FIELD);
1781 vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
1782 add_header (ct, np, vp);
1783 }
1784
1785 /*
1786 * output the Content-Transfer-Encoding
1787 * If using EAI and message body is 7-bit, force 8-bit C-T-E.
1788 */
1789 if (header_encoding == CE_8BIT && ct->c_encoding == CE_7BIT) {
1790 ct->c_encoding = CE_8BIT;
1791 }
1792
1793 switch (ct->c_encoding) {
1794 case CE_7BIT:
1795 /* Nothing to output */
1796 break;
1797
1798 case CE_8BIT:
1799 np = mh_xstrdup(ENCODING_FIELD);
1800 vp = concat (" ", "8bit", "\n", NULL);
1801 add_header (ct, np, vp);
1802 break;
1803
1804 case CE_QUOTED:
1805 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1806 adios (NULL, "internal error, invalid encoding");
1807
1808 np = mh_xstrdup(ENCODING_FIELD);
1809 vp = concat (" ", "quoted-printable", "\n", NULL);
1810 add_header (ct, np, vp);
1811 break;
1812
1813 case CE_BASE64:
1814 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1815 adios (NULL, "internal error, invalid encoding");
1816
1817 np = mh_xstrdup(ENCODING_FIELD);
1818 vp = concat (" ", "base64", "\n", NULL);
1819 add_header (ct, np, vp);
1820 break;
1821
1822 case CE_BINARY:
1823 if (ct->c_type == CT_MESSAGE)
1824 adios (NULL, "internal error, invalid encoding");
1825
1826 np = mh_xstrdup(ENCODING_FIELD);
1827 vp = concat (" ", "binary", "\n", NULL);
1828 add_header (ct, np, vp);
1829 break;
1830
1831 default:
1832 adios (NULL, "unknown transfer encoding in content");
1833 break;
1834 }
1835
1836 /*
1837 * Additional content specific header processing
1838 */
1839 switch (ct->c_type) {
1840 case CT_MULTIPART:
1841 {
1842 struct multipart *m;
1843 struct part *part;
1844
1845 m = (struct multipart *) ct->c_ctparams;
1846 for (part = m->mp_parts; part; part = part->mp_next) {
1847 CT p;
1848
1849 p = part->mp_part;
1850 build_headers (p, header_encoding);
1851 }
1852 }
1853 break;
1854
1855 case CT_MESSAGE:
1856 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1857 struct exbody *e;
1858
1859 e = (struct exbody *) ct->c_ctparams;
1860 build_headers (e->eb_content, header_encoding);
1861 }
1862 break;
1863
1864 default:
1865 /* Nothing to do */
1866 break;
1867 }
1868
1869 return OK;
1870 }
1871
1872
1873 static char nib2b64[0x40+1] =
1874 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1875
1876 static char *
1877 calculate_digest (CT ct, int asciiP)
1878 {
1879 int cc;
1880 char *vp, *op;
1881 unsigned char *dp;
1882 unsigned char digest[16];
1883 unsigned char outbuf[25];
1884 MD5_CTX mdContext;
1885 CE ce = &ct->c_cefile;
1886 char *infilename = ce->ce_file ? ce->ce_file : ct->c_file;
1887 FILE *in;
1888
1889 /* open content */
1890 if ((in = fopen (infilename, "r")) == NULL)
1891 adios (infilename, "unable to open for reading");
1892
1893 /* Initialize md5 context */
1894 MD5Init (&mdContext);
1895
1896 /* calculate md5 message digest */
1897 if (asciiP) {
1898 char *bufp = NULL;
1899 size_t buflen;
1900 ssize_t gotlen;
1901 while ((gotlen = getline(&bufp, &buflen, in)) != -1) {
1902 char c, *cp;
1903
1904 cp = bufp + gotlen - 1;
1905 if ((c = *cp) == '\n')
1906 gotlen--;
1907
1908 MD5Update (&mdContext, (unsigned char *) bufp,
1909 (unsigned int) gotlen);
1910
1911 if (c == '\n')
1912 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
1913 }
1914 } else {
1915 char buffer[BUFSIZ];
1916 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
1917 MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
1918 }
1919
1920 /* md5 finalization. Write digest and zero md5 context */
1921 MD5Final (digest, &mdContext);
1922
1923 /* close content */
1924 fclose (in);
1925
1926 /* print debugging info */
1927 if (debugsw) {
1928 unsigned char *ep;
1929
1930 fprintf (stderr, "MD5 digest=");
1931 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1932 dp < ep; dp++)
1933 fprintf (stderr, "%02x", *dp & 0xff);
1934 fprintf (stderr, "\n");
1935 }
1936
1937 /* encode the digest using base64 */
1938 for (dp = digest, op = (char *) outbuf,
1939 cc = sizeof(digest) / sizeof(digest[0]);
1940 cc > 0; cc -= 3, op += 4) {
1941 unsigned long bits;
1942 char *bp;
1943
1944 bits = (*dp++ & 0xff) << 16;
1945 if (cc > 1) {
1946 bits |= (*dp++ & 0xff) << 8;
1947 if (cc > 2)
1948 bits |= *dp++ & 0xff;
1949 }
1950
1951 for (bp = op + 4; bp > op; bits >>= 6)
1952 *--bp = nib2b64[bits & 0x3f];
1953 if (cc < 3) {
1954 *(op + 3) = '=';
1955 if (cc < 2)
1956 *(op + 2) = '=';
1957 }
1958 }
1959
1960 /* null terminate string */
1961 outbuf[24] = '\0';
1962
1963 /* now make copy and return string */
1964 vp = concat (" ", outbuf, "\n", NULL);
1965 return vp;
1966 }
1967
1968 /*
1969 * Set things up for the content structure for file "filename" that
1970 * we want to attach
1971 */
1972
1973 static void
1974 setup_attach_content(CT ct, char *filename)
1975 {
1976 char *type, *simplename = r1bindex(filename, '/');
1977 struct str2init *s2i;
1978 PM pm;
1979
1980 if (! (type = mime_type(filename))) {
1981 adios(NULL, "Unable to determine MIME type of \"%s\"", filename);
1982 }
1983
1984 /*
1985 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1986 * since we're just feeding it a MIME type we have to add those ourself.
1987 * Map that to a valid content-type label and call any initialization
1988 * function.
1989 */
1990
1991 if (get_ctinfo(type, ct, 0) == NOTOK)
1992 done(1);
1993
1994 free(type);
1995
1996 for (s2i = str2cts; s2i->si_key; s2i++)
1997 if (strcasecmp(ct->c_ctinfo.ci_type, s2i->si_key) == 0)
1998 break;
1999 if (!s2i->si_key && !uprf(ct->c_ctinfo.ci_type, "X-"))
2000 s2i++;
2001
2002 /*
2003 * Make sure the type isn't incompatible with what we can handle
2004 */
2005
2006 switch (ct->c_type = s2i->si_val) {
2007 case CT_MULTIPART:
2008 adios (NULL, "multipart types must be specified by mhbuild directives");
2009 /* NOTREACHED */
2010
2011 case CT_MESSAGE:
2012 if (strcasecmp(ct->c_ctinfo.ci_subtype, "partial") == 0)
2013 adios(NULL, "Sorry, %s/%s isn't supported", ct->c_ctinfo.ci_type,
2014 ct->c_ctinfo.ci_subtype);
2015 if (strcasecmp(ct->c_ctinfo.ci_subtype, "external-body") == 0)
2016 adios(NULL, "external-body messages must be specified "
2017 "by mhbuild directives");
2018 /* Fall through */
2019
2020 default:
2021 /*
2022 * This sets the subtype, if it's significant
2023 */
2024 if ((ct->c_ctinitfnx = s2i->si_init))
2025 (*ct->c_ctinitfnx)(ct);
2026 break;
2027 }
2028
2029 /*
2030 * Feed in a few attributes; specifically, the name attribute, the
2031 * content-description, and the content-disposition.
2032 */
2033
2034 for (pm = ct->c_ctinfo.ci_first_pm; pm; pm = pm->pm_next) {
2035 if (strcasecmp(pm->pm_name, "name") == 0) {
2036 mh_xfree(pm->pm_value);
2037 pm->pm_value = mh_xstrdup(simplename);
2038 break;
2039 }
2040 }
2041
2042 if (pm == NULL)
2043 add_param(&ct->c_ctinfo.ci_first_pm, &ct->c_ctinfo.ci_last_pm,
2044 "name", simplename, 0);
2045
2046 ct->c_descr = mh_xstrdup(simplename);
2047 ct->c_descr = add("\n", ct->c_descr);
2048 ct->c_cefile.ce_file = mh_xstrdup(filename);
2049
2050 set_disposition (ct);
2051
2052 add_param(&ct->c_dispo_first, &ct->c_dispo_last, "filename", simplename, 0);
2053 }
2054
2055 /*
2056 * If disposition type hasn't already been set in ct:
2057 * Look for mhbuild-disposition-<type>/<subtype> entry
2058 * that specifies Content-Disposition type. Only
2059 * 'attachment' and 'inline' are allowed. Default to
2060 * 'attachment'.
2061 */
2062 void
2063 set_disposition (CT ct) {
2064 if (ct->c_dispo_type == NULL) {
2065 char *cp = context_find_by_type ("disposition", ct->c_ctinfo.ci_type,
2066 ct->c_ctinfo.ci_subtype);
2067
2068 if (cp && strcasecmp (cp, "attachment") &&
2069 strcasecmp (cp, "inline")) {
2070 admonish (NULL, "configuration problem: %s-disposition-%s%s%s "
2071 "specifies '%s' but only 'attachment' and 'inline' are "
2072 "allowed", invo_name,
2073 ct->c_ctinfo.ci_type,
2074 ct->c_ctinfo.ci_subtype ? "/" : "",
2075 ct->c_ctinfo.ci_subtype ? ct->c_ctinfo.ci_subtype : "",
2076 cp);
2077 }
2078
2079 if (!cp)
2080 cp = "attachment";
2081 ct->c_dispo_type = mh_xstrdup(cp);
2082 }
2083 }
2084
2085 /*
2086 * Set text content charset if it was unspecified. contains8bit
2087 * selctions:
2088 * 0: content does not contain 8-bit characters
2089 * 1: content contains 8-bit characters
2090 * -1: ignore content and use user's locale to determine charset
2091 */
2092 void
2093 set_charset (CT ct, int contains8bit) {
2094 if (ct->c_type == CT_TEXT) {
2095 struct text *t;
2096
2097 if (ct->c_ctparams == NULL) {
2098 NEW0(t);
2099 ct->c_ctparams = t;
2100 t->tx_charset = CHARSET_UNSPECIFIED;
2101 } else {
2102 t = (struct text *) ct->c_ctparams;
2103 }
2104
2105 if (t->tx_charset == CHARSET_UNSPECIFIED) {
2106 CI ci = &ct->c_ctinfo;
2107 char *eightbitcharset = write_charset_8bit();
2108 char *charset = contains8bit ? eightbitcharset : "us-ascii";
2109
2110 if (contains8bit == 1 &&
2111 strcasecmp (eightbitcharset, "US-ASCII") == 0) {
2112 adios (NULL, "Text content contains 8 bit characters, but "
2113 "character set is US-ASCII");
2114 }
2115
2116 add_param (&ci->ci_first_pm, &ci->ci_last_pm, "charset", charset,
2117 0);
2118
2119 t->tx_charset = CHARSET_SPECIFIED;
2120 }
2121 }
2122 }
2123
2124
2125 /*
2126 * Look at all of the replied-to message parts and expand any that
2127 * are matched by a pseudoheader. Except don't descend into
2128 * message parts.
2129 */
2130 void
2131 expand_pseudoheaders (CT ct, struct multipart *m, const char *infile,
2132 const convert_list *convert_head) {
2133 /* text_plain_ct is used to concatenate all of the text/plain
2134 replies into one part, instead of having each one in a separate
2135 part. */
2136 CT text_plain_ct = NULL;
2137
2138 switch (ct->c_type) {
2139 case CT_MULTIPART: {
2140 struct multipart *mp = (struct multipart *) ct->c_ctparams;
2141 struct part *part;
2142
2143 if (ct->c_subtype == MULTI_ALTERNATE) {
2144 int matched = 0;
2145
2146 /* The parts are in descending priority order (defined by
2147 RFC 2046 Sec. 5.1.4) because they were reversed by
2148 parse_mime (). So, stop looking for matches with
2149 immediate subparts after the first match of an
2150 alternative. */
2151 for (part = mp->mp_parts; ! matched && part; part = part->mp_next) {
2152 char *type_subtype =
2153 concat (part->mp_part->c_ctinfo.ci_type, "/",
2154 part->mp_part->c_ctinfo.ci_subtype, NULL);
2155
2156 if (part->mp_part->c_type == CT_MULTIPART) {
2157 expand_pseudoheaders (part->mp_part, m, infile,
2158 convert_head);
2159 } else {
2160 const convert_list *c;
2161
2162 for (c = convert_head; c; c = c->next) {
2163 if (! strcasecmp (type_subtype, c->type)) {
2164 expand_pseudoheader (part->mp_part, &text_plain_ct,
2165 m, infile,
2166 c->type, c->argstring);
2167 matched = 1;
2168 break;
2169 }
2170 }
2171 }
2172 free (type_subtype);
2173 }
2174 } else {
2175 for (part = mp->mp_parts; part; part = part->mp_next) {
2176 expand_pseudoheaders (part->mp_part, m, infile, convert_head);
2177 }
2178 }
2179 break;
2180 }
2181
2182 default: {
2183 char *type_subtype =
2184 concat (ct->c_ctinfo.ci_type, "/", ct->c_ctinfo.ci_subtype,
2185 NULL);
2186 const convert_list *c;
2187
2188 for (c = convert_head; c; c = c->next) {
2189 if (! strcasecmp (type_subtype, c->type)) {
2190 expand_pseudoheader (ct, &text_plain_ct, m, infile, c->type,
2191 c->argstring);
2192 break;
2193 }
2194 }
2195 free (type_subtype);
2196 break;
2197 }
2198 }
2199 }
2200
2201
2202 /*
2203 * Expand a single pseudoheader. It's for the specified type.
2204 */
2205 void
2206 expand_pseudoheader (CT ct, CT *text_plain_ct, struct multipart *m,
2207 const char *infile, const char *type,
2208 const char *argstring) {
2209 char *reply_file;
2210 FILE *reply_fp = NULL;
2211 char *convert, *type_p, *subtype_p;
2212 char *convert_command;
2213 char *charset = NULL;
2214 char *cp;
2215 struct str2init *s2i;
2216 CT reply_ct;
2217 struct part *part;
2218 int eightbit = 0;
2219 int status;
2220
2221 type_p = getcpy (type);
2222 if ((subtype_p = strchr (type_p, '/'))) {
2223 *subtype_p++ = '\0';
2224 convert = context_find_by_type ("convert", type_p, subtype_p);
2225 } else {
2226 free (type_p);
2227 type_p = concat ("mhbuild-convert-", type, NULL);
2228 convert = context_find (type_p);
2229 }
2230 free (type_p);
2231
2232 if (! (convert)) {
2233 /* No mhbuild-convert- entry in mhn.defaults or profile
2234 for type. */
2235 return;
2236 }
2237 /* reply_file is used to pass the output of the convert. */
2238 reply_file = getcpy (m_mktemp2 (NULL, invo_name, NULL, NULL));
2239 convert_command =
2240 concat (convert, " ", argstring ? argstring : "", " >", reply_file,
2241 NULL);
2242
2243 /* Convert here . . . */
2244 ct->c_storeproc = mh_xstrdup(convert_command);
2245 ct->c_umask = ~m_gmprot ();
2246
2247 if ((status = show_content_aux (ct, 0, convert_command, NULL, NULL)) !=
2248 OK) {
2249 admonish (NULL, "store of %s content failed", type);
2250 }
2251 free (convert_command);
2252
2253 /* Fill out the the new ct, reply_ct. */
2254 NEW0(reply_ct);
2255 init_decoded_content (reply_ct, infile);
2256
2257 if (extract_headers (reply_ct, reply_file, &reply_fp) == NOTOK) {
2258 free (reply_file);
2259 admonish (NULL,
2260 "failed to extract headers from convert output in %s",
2261 reply_file);
2262 return;
2263 }
2264
2265 /* For text content only, see if it is 8-bit text. */
2266 if (reply_ct->c_type == CT_TEXT) {
2267 int fd;
2268
2269 if ((fd = open (reply_file, O_RDONLY)) == NOTOK ||
2270 scan_input (fd, &eightbit) == NOTOK) {
2271 free (reply_file);
2272 admonish (NULL, "failed to read %s", reply_file);
2273 return;
2274 }
2275 (void) close (fd);
2276 }
2277
2278 /* This sets reply_ct->c_ctparams, and reply_ct->c_termproc if the
2279 charset can't be handled natively. */
2280 for (s2i = str2cts; s2i->si_key; s2i++) {
2281 if (strcasecmp(reply_ct->c_ctinfo.ci_type, s2i->si_key) == 0) {
2282 break;
2283 }
2284 }
2285
2286 if ((reply_ct->c_ctinitfnx = s2i->si_init)) {
2287 (*reply_ct->c_ctinitfnx)(reply_ct);
2288 }
2289
2290 if ((cp =
2291 get_param (reply_ct->c_ctinfo.ci_first_pm, "charset", '?', 1))) {
2292 /* The reply Content-Type had the charset. */
2293 charset = cp;
2294 } else {
2295 set_charset (reply_ct, -1);
2296 charset = get_param (reply_ct->c_ctinfo.ci_first_pm, "charset", '?', 1);
2297 if (reply_ct->c_reqencoding == CE_UNKNOWN &&
2298 reply_ct->c_type == CT_TEXT) {
2299 /* Assume that 8bit is sufficient (for text). In other words,
2300 don't allow it to be encoded as quoted printable if lines
2301 are too long. This also sidesteps the check for whether
2302 it needs to be encoded as binary; instead, it relies on
2303 the applicable mhbuild-convert-text directive to ensure
2304 that the resultant text is not binary. */
2305 reply_ct->c_reqencoding = eightbit ? CE_8BIT : CE_7BIT;
2306 }
2307 }
2308
2309 /* Concatenate text/plain parts. */
2310 if (reply_ct->c_type == CT_TEXT &&
2311 reply_ct->c_subtype == TEXT_PLAIN) {
2312 if (! *text_plain_ct && m->mp_parts && m->mp_parts->mp_part &&
2313 m->mp_parts->mp_part->c_type == CT_TEXT &&
2314 m->mp_parts->mp_part->c_subtype == TEXT_PLAIN) {
2315 *text_plain_ct = m->mp_parts->mp_part;
2316 /* Make sure that the charset is set in the text/plain
2317 part. */
2318 set_charset (*text_plain_ct, -1);
2319 if ((*text_plain_ct)->c_reqencoding == CE_UNKNOWN) {
2320 /* Assume that 8bit is sufficient (for text). In other words,
2321 don't allow it to be encoded as quoted printable if lines
2322 are too long. This also sidesteps the check for whether
2323 it needs to be encoded as binary; instead, it relies on
2324 the applicable mhbuild-convert-text directive to ensure
2325 that the resultant text is not binary. */
2326 (*text_plain_ct)->c_reqencoding =
2327 eightbit ? CE_8BIT : CE_7BIT;
2328 }
2329 }
2330
2331 if (*text_plain_ct) {
2332 /* Only concatenate if the charsets are identical. */
2333 char *text_plain_ct_charset =
2334 get_param ((*text_plain_ct)->c_ctinfo.ci_first_pm, "charset",
2335 '?', 1);
2336
2337 if (strcasecmp (text_plain_ct_charset, charset) == 0) {
2338 /* Append this text/plain reply to the first one.
2339 If there's a problem anywhere along the way,
2340 instead attach it is a separate part. */
2341 int text_plain_reply =
2342 open ((*text_plain_ct)->c_cefile.ce_file,
2343 O_WRONLY | O_APPEND);
2344 int addl_reply = open (reply_file, O_RDONLY);
2345
2346 if (text_plain_reply != NOTOK && addl_reply != NOTOK) {
2347 /* Insert blank line before each addl part. */
2348 /* It would be nice not to do this for the first one. */
2349 if (write (text_plain_reply, "\n", 1) == 1) {
2350 /* Copy the text from the new reply and
2351 then free its Content struct. */
2352 cpydata (addl_reply, text_plain_reply,
2353 (*text_plain_ct)->c_cefile.ce_file,
2354 reply_file);
2355 if (close (text_plain_reply) == OK &&
2356 close (addl_reply) == OK) {
2357 /* If appended text needed 8-bit but first text didn't,
2358 propagate the 8-bit indication. */
2359 if ((*text_plain_ct)->c_reqencoding == CE_7BIT &&
2360 reply_ct->c_reqencoding == CE_8BIT) {
2361 (*text_plain_ct)->c_reqencoding = CE_8BIT;
2362 }
2363
2364 if (reply_fp) { fclose (reply_fp); }
2365 free (reply_file);
2366 free_content (reply_ct);
2367 return;
2368 }
2369 }
2370 }
2371 }
2372 } else {
2373 *text_plain_ct = reply_ct;
2374 }
2375 }
2376
2377 reply_ct->c_cefile.ce_file = reply_file;
2378 reply_ct->c_cefile.ce_fp = reply_fp;
2379 reply_ct->c_cefile.ce_unlink = 1;
2380
2381 /* Attach the new part to the parent multipart/mixed, "m". */
2382 NEW0(part);
2383 part->mp_part = reply_ct;
2384 if (m->mp_parts) {
2385 struct part *p;
2386
2387 for (p = m->mp_parts; p && p->mp_next; p = p->mp_next) { continue; }
2388 p->mp_next = part;
2389 } else {
2390 m->mp_parts = part;
2391 }
2392 }
2393
2394
2395 /* Extract any Content-Type header from beginning of convert output. */
2396 int
2397 extract_headers (CT ct, char *reply_file, FILE **reply_fp) {
2398 char *buffer = NULL, *cp, *end_of_header;
2399 int found_header = 0;
2400 struct stat statbuf;
2401
2402 /* Read the convert reply from the file to memory. */
2403 if (stat (reply_file, &statbuf) == NOTOK) {
2404 admonish (reply_file, "failed to stat");
2405 goto failed_to_extract_ct;
2406 }
2407
2408 buffer = mh_xmalloc (statbuf.st_size + 1);
2409
2410 if ((*reply_fp = fopen (reply_file, "r+")) == NULL ||
2411 fread (buffer, 1, (size_t) statbuf.st_size, *reply_fp) <
2412 (size_t) statbuf.st_size) {
2413 admonish (reply_file, "failed to read");
2414 goto failed_to_extract_ct;
2415 }
2416 buffer[statbuf.st_size] = '\0';
2417
2418 /* Look for a header in the convert reply. */
2419 if (strncasecmp (buffer, TYPE_FIELD, strlen (TYPE_FIELD)) == 0 &&
2420 buffer[strlen (TYPE_FIELD)] == ':') {
2421 if ((end_of_header = strstr (buffer, "\r\n\r\n"))) {
2422 end_of_header += 2;
2423 found_header = 1;
2424 } else if ((end_of_header = strstr (buffer, "\n\n"))) {
2425 ++end_of_header;
2426 found_header = 1;
2427 }
2428 }
2429
2430 if (found_header) {
2431 CT tmp_ct;
2432 char *tmp_file;
2433 FILE *tmp_f;
2434 size_t n;
2435
2436 /* Truncate buffer to just the C-T. */
2437 *end_of_header = '\0';
2438 n = strlen (buffer);
2439
2440 if (get_ctinfo (buffer + 14, ct, 0) != OK) {
2441 admonish (NULL, "unable to get content info for reply");
2442 goto failed_to_extract_ct;
2443 }
2444
2445 /* Hack. Use parse_mime() to detect the type/subtype of the
2446 reply, which we'll use below. */
2447 tmp_file = getcpy (m_mktemp2 (NULL, invo_name, NULL, NULL));
2448 if ((tmp_f = fopen (tmp_file, "w")) &&
2449 fwrite (buffer, 1, n, tmp_f) == n) {
2450 fclose (tmp_f);
2451 } else {
2452 goto failed_to_extract_ct;
2453 }
2454 tmp_ct = parse_mime (tmp_file);
2455
2456 if (tmp_ct) {
2457 /* The type and subtype were detected from the reply
2458 using parse_mime() above. */
2459 ct->c_type = tmp_ct->c_type;
2460 ct->c_subtype = tmp_ct->c_subtype;
2461 free_content (tmp_ct);
2462 }
2463
2464 free (tmp_file);
2465
2466 /* Rewrite the content without the header. */
2467 cp = end_of_header + 1;
2468 rewind (*reply_fp);
2469
2470 if (fwrite (cp, 1, statbuf.st_size - (cp - buffer), *reply_fp) <
2471 (size_t) (statbuf.st_size - (cp - buffer))) {
2472 admonish (reply_file, "failed to write");
2473 goto failed_to_extract_ct;
2474 }
2475
2476 if (ftruncate (fileno (*reply_fp), statbuf.st_size - (cp - buffer)) !=
2477 0) {
2478 advise (reply_file, "ftruncate");
2479 goto failed_to_extract_ct;
2480 }
2481 } else {
2482 /* No header section, assume the reply is text/plain. */
2483 ct->c_type = CT_TEXT;
2484 ct->c_subtype = TEXT_PLAIN;
2485 if (get_ctinfo ("text/plain", ct, 0) == NOTOK) {
2486 /* This never should fail, but just in case. */
2487 adios (NULL, "unable to get content info for reply");
2488 }
2489 }
2490
2491 /* free_encoding() will close reply_fp, which is passed through
2492 ct->c_cefile.ce_fp. */
2493 free (buffer);
2494 return OK;
2495
2496 failed_to_extract_ct:
2497 if (*reply_fp) { fclose (*reply_fp); }
2498 free (buffer);
2499 return NOTOK;
2500 }