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