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