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