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