1 /* mhbuildsbr.c -- routines to expand/translate MIME composition files
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.
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.
17 #include "sbr/m_convert.h"
18 #include "sbr/getfolder.h"
19 #include "sbr/folder_read.h"
20 #include "sbr/folder_free.h"
21 #include "sbr/context_find.h"
22 #include "sbr/brkstring.h"
23 #include "sbr/pidstatus.h"
25 #include "sbr/error.h"
30 #include "h/fmt_scan.h"
32 #include "h/mhparse.h"
35 #include "h/mhcachesbr.h"
37 #include "sbr/m_mktemp.h"
38 #include "sbr/message_id.h"
39 #include "sbr/mime_type.h"
41 #include "mhshowsbr.h"
43 #ifdef HAVE_SYS_TIME_H
44 # include <sys/time.h>
53 extern bool contentidsw
;
55 static char prefix
[] = "----- =_aaaaaaaaaa";
59 struct attach_list
*next
;
62 typedef struct convert_list
{
66 struct convert_list
*next
;
73 static int init_decoded_content (CT
, const char *);
74 static void setup_attach_content(CT
, char *);
75 static void set_disposition (CT
);
76 static void set_charset (CT
, int);
77 static void expand_pseudoheaders (CT
, struct multipart
*, const char *,
78 const convert_list
*);
79 static void expand_pseudoheader (CT
, CT
*, struct multipart
*, const char *,
80 const char *, const char *);
81 static char *fgetstr (char *, int, FILE *);
82 static int user_content (FILE *, char *, CT
*, const char *infilename
);
83 static void set_id (CT
, int);
84 static int compose_content (CT
, int);
85 static int scan_content (CT
, size_t);
86 static int build_headers (CT
, int);
87 static char *calculate_digest (CT
, int);
88 static int extract_headers (CT
, char *, FILE **);
91 static unsigned char directives_stack
[32];
92 static unsigned int directives_index
;
97 return directives_stack
[directives_index
];
101 directive_onoff(int onoff
)
103 if (directives_index
>= sizeof(directives_stack
) - 1) {
104 fprintf(stderr
, "mhbuild: #on/off overflow, continuing\n");
107 directives_stack
[++directives_index
] = onoff
;
111 directive_init(int onoff
)
113 directives_index
= 0;
114 directives_stack
[0] = onoff
;
120 if (directives_index
> 0)
123 fprintf(stderr
, "mhbuild: #pop underflow, continuing\n");
127 * Main routine for translating composition file
128 * into valid MIME message. It translates the draft
129 * into a content structure (actually a tree of content
130 * structures). This message then can be manipulated
131 * in various ways, including being output via
136 build_mime (char *infile
, int autobuild
, int dist
, int directives
,
137 int header_encoding
, size_t maxunencoded
, int verbose
)
140 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
147 m_getfld_state_t gstate
;
148 struct attach_list
*attach_head
= NULL
, *attach_tail
= NULL
, *at_entry
;
149 convert_list
*convert_head
= NULL
, *convert_tail
= NULL
, *convert
;
151 directive_init(directives
);
153 umask (~m_gmprot ());
155 /* open the composition draft */
156 if ((in
= fopen (infile
, "r")) == NULL
)
157 adios (infile
, "unable to open for reading");
160 * Allocate space for primary (outside) content
165 * Allocate structure for handling decoded content
166 * for this part. We don't really need this, but
167 * allocate it to remain consistent.
169 init_decoded_content (ct
, infile
);
172 * Parse some of the header fields in the composition
173 * draft into the linked list of header fields for
174 * the new MIME message.
176 gstate
= m_getfld_state_init(in
);
177 m_getfld_track_filepos2(&gstate
);
178 for (compnum
= 1;;) {
179 int bufsz
= sizeof buf
;
180 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
185 /* abort if draft has Mime-Version or C-T-E header field */
186 if (strcasecmp (name
, VRSN_FIELD
) == 0 ||
187 strcasecmp (name
, ENCODING_FIELD
) == 0) {
193 die("draft shouldn't contain %s: field", name
);
196 /* ignore any Content-Type fields in the header */
197 if (!strcasecmp (name
, TYPE_FIELD
)) {
198 while (state
== FLDPLUS
) {
200 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
205 /* get copies of the buffers */
206 np
= mh_xstrdup(name
);
207 vp
= mh_xstrdup(buf
);
209 /* if necessary, get rest of field */
210 while (state
== FLDPLUS
) {
212 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
213 vp
= add (buf
, vp
); /* add to previous value */
217 * Now add the header data to the list, unless it's an attach
218 * header; in that case, add it to our attach list
221 if (strcasecmp(ATTACH_FIELD
, np
) == 0 ||
222 strcasecmp(ATTACH_FIELD_ALT
, np
) == 0) {
223 struct attach_list
*entry
;
224 char *s
= vp
, *e
= vp
+ strlen(vp
) - 1;
228 * Make sure we can find the start of this filename.
229 * If it's blank, we skip completely. Otherwise, strip
230 * off any leading spaces and trailing newlines.
233 while (isspace((unsigned char) *s
))
236 while (e
> s
&& *e
== '\n')
245 entry
->filename
= mh_xstrdup(s
);
250 attach_tail
->next
= entry
;
253 attach_head
= attach_tail
= entry
;
255 } else if (strncasecmp(MHBUILD_FILE_PSEUDOHEADER
, np
,
256 LEN(MHBUILD_FILE_PSEUDOHEADER
)) == 0) {
258 * Nmh-mhbuild-file-text/calendar: /home/user/Mail/inbox/9
260 char *type
= np
+ LEN(MHBUILD_FILE_PSEUDOHEADER
);
263 /* vp should begin with a space because m_getfld2()
264 includes the space after the colon in buf. */
265 while (isspace((unsigned char) *filename
)) { ++filename
; }
266 /* Trim trailing newline and any other whitespace. */
269 for (convert
= convert_head
; convert
; convert
= convert
->next
) {
270 if (strcasecmp (convert
->type
, type
) == 0) { break; }
273 if (convert
->filename
&&
274 strcasecmp (convert
->filename
, filename
)) {
275 die("Multiple %s headers with different files"
276 " not allowed", type
);
278 convert
->filename
= mh_xstrdup(filename
);
282 convert
->filename
= mh_xstrdup(filename
);
283 convert
->type
= mh_xstrdup(type
);
286 convert_tail
->next
= convert
;
288 convert_head
= convert
;
290 convert_tail
= convert
;
295 } else if (strncasecmp(MHBUILD_ARGS_PSEUDOHEADER
, np
,
296 LEN(MHBUILD_ARGS_PSEUDOHEADER
)) == 0) {
298 * Nmh-mhbuild-args-text/calendar: -reply accept
300 char *type
= np
+ LEN(MHBUILD_ARGS_PSEUDOHEADER
);
301 char *argstring
= vp
;
303 /* vp should begin with a space because m_getfld2()
304 includes the space after the colon in buf. */
305 while (isspace((unsigned char) *argstring
)) { ++argstring
; }
306 /* Trim trailing newline and any other whitespace. */
309 for (convert
= convert_head
; convert
; convert
= convert
->next
) {
310 if (strcasecmp (convert
->type
, type
) == 0) { break; }
313 if (convert
->argstring
&&
314 strcasecmp (convert
->argstring
, argstring
)) {
315 die("Multiple %s headers with different "
316 "argstrings not allowed", type
);
318 convert
->argstring
= mh_xstrdup(argstring
);
322 convert
->type
= mh_xstrdup(type
);
323 convert
->argstring
= mh_xstrdup(argstring
);
326 convert_tail
->next
= convert
;
328 convert_head
= convert
;
330 convert_tail
= convert
;
336 add_header (ct
, np
, vp
);
340 /* if this wasn't the last header field, then continue */
344 fseek (in
, (long) (-strlen (buf
)), SEEK_CUR
);
351 die("message format error in component #%d", compnum
);
354 die("getfld() returned %d", state
);
358 m_getfld_state_destroy (&gstate
);
360 if (header_encoding
!= CE_8BIT
) {
362 * Iterate through the list of headers and call the function to MIME-ify
366 for (hp
= ct
->c_first_hf
; hp
!= NULL
; hp
= hp
->next
) {
367 if (encode_rfc2047(hp
->name
, &hp
->value
, header_encoding
, NULL
)) {
368 die("Unable to encode header \"%s\"", hp
->name
);
374 * Now add the MIME-Version header field
375 * to the list of header fields.
379 np
= mh_xstrdup(VRSN_FIELD
);
380 vp
= concat (" ", VRSN_VALUE
, "\n", NULL
);
381 add_header (ct
, np
, vp
);
385 * We initially assume we will find multiple contents in the
386 * draft. So create a multipart/mixed content to hold everything.
387 * We can remove this later, if it is not needed.
389 if (get_ctinfo ("multipart/mixed", ct
, 0) == NOTOK
)
391 ct
->c_type
= CT_MULTIPART
;
392 ct
->c_subtype
= MULTI_MIXED
;
395 ct
->c_ctparams
= (void *) m
;
399 * read and parse the composition file
400 * and the directives it contains.
402 while (fgetstr (buf
, sizeof(buf
) - 1, in
)) {
406 if (user_content (in
, buf
, &p
, infile
) == DONE
) {
407 inform("ignoring spurious #end, continuing...");
420 * Add any Attach headers to the list of MIME parts at the end of the
424 for (at_entry
= attach_head
; at_entry
; ) {
425 struct attach_list
*at_prev
= at_entry
;
429 if (access(at_entry
->filename
, R_OK
) != 0) {
430 adios("reading", "Unable to open %s for", at_entry
->filename
);
434 init_decoded_content(p
, infile
);
437 * Initialize our content structure based on the filename,
438 * and fill in all of the relevant fields. Also place MIME
439 * parameters in the attributes array.
442 setup_attach_content(p
, at_entry
->filename
);
449 at_entry
= at_entry
->next
;
450 free(at_prev
->filename
);
455 * Handle the mhbuild pseudoheaders, which deal with specific
462 set_done(freects_done
);
464 /* In case there are multiple calls that land here, prevent leak. */
465 for (ctp
= cts
; ctp
&& *ctp
; ++ctp
) { free_content (*ctp
); }
468 /* Extract the type part (as a CT) from filename. */
469 cts
= mh_xcalloc(2, sizeof *cts
);
470 if (! (cts
[0] = parse_mime (convert_head
->filename
))) {
471 die("failed to parse %s", convert_head
->filename
);
474 expand_pseudoheaders (cts
[0], m
, infile
, convert_head
);
476 /* Free the convert list. */
477 for (convert
= convert_head
; convert
; convert
= next
) {
478 next
= convert
->next
;
479 free (convert
->type
);
480 free (convert
->filename
);
481 free (convert
->argstring
);
488 * To allow for empty message bodies, if we've found NO content at all
489 * yet cook up an empty text/plain part.
498 init_decoded_content(p
, infile
);
500 if (get_ctinfo ("text/plain", p
, 0) == NOTOK
)
504 p
->c_subtype
= TEXT_PLAIN
;
505 p
->c_encoding
= CE_7BIT
;
507 * Sigh. ce_file contains the "decoded" contents of this part.
508 * So this seems like the best option available since we're going
509 * to call scan_content() on this.
511 p
->c_cefile
.ce_file
= mh_xstrdup("/dev/null");
512 p
->c_begin
= ftell(in
);
513 p
->c_end
= ftell(in
);
516 t
->tx_charset
= CHARSET_SPECIFIED
;
525 * close the composition draft since
526 * it's not needed any longer.
531 * If only one content was found, then remove and
532 * free the outer multipart content.
534 if (!m
->mp_parts
->mp_next
) {
537 p
= m
->mp_parts
->mp_part
;
538 m
->mp_parts
->mp_part
= NULL
;
540 /* move header fields */
541 p
->c_first_hf
= ct
->c_first_hf
;
542 p
->c_last_hf
= ct
->c_last_hf
;
543 ct
->c_first_hf
= NULL
;
544 ct
->c_last_hf
= NULL
;
553 * Fill out, or expand directives. Parse and execute
554 * commands specified by profile composition strings.
556 compose_content (ct
, verbose
);
558 if ((cp
= strchr(prefix
, 'a')) == NULL
)
559 die("internal error(4)");
562 * If using EAI, force 8-bit charset.
564 if (header_encoding
== CE_8BIT
) {
569 * Scan the contents. Choose a transfer encoding, and
570 * check if prefix for multipart boundary clashes with
571 * any of the contents.
573 while (scan_content (ct
, maxunencoded
) == NOTOK
) {
578 die("giving up trying to find a unique delimiter string");
583 /* Build the rest of the header field structures */
585 build_headers (ct
, header_encoding
);
592 * Set up structures for placing unencoded
593 * content when building parts.
597 init_decoded_content (CT ct
, const char *filename
)
599 ct
->c_ceopenfnx
= open7Bit
; /* since unencoded */
600 ct
->c_ceclosefnx
= close_encoding
;
601 ct
->c_cesizefnx
= NULL
; /* since unencoded */
602 ct
->c_encoding
= CE_7BIT
; /* Seems like a reasonable default */
603 ct
->c_file
= mh_xstrdup(FENDNULL(filename
));
610 fgetstr (char *s
, int n
, FILE *stream
)
616 for (cp
= s
; cp
< ep
;) {
619 if (!fgets (cp
, n
, stream
))
620 return cp
== s
? NULL
: s
; /* "\\\nEOF" ignored. */
622 if (! do_direct() || (cp
== s
&& *cp
!= '#'))
623 return s
; /* Plaintext line. */
627 break; /* Can't contain "\\\n". */
628 cp
+= len
- 1; /* Just before NUL. */
629 if (*cp
-- != '\n' || *cp
!= '\\')
631 *cp
= '\0'; /* Erase the trailing "\\\n". */
635 if (strcmp(s
, "#on\n") == 0) {
637 } else if (strcmp(s
, "#off\n") == 0) {
639 } else if (strcmp(s
, "#pop\n") == 0) {
649 * Parse the composition draft for text and directives.
650 * Do initial setup of Content structure.
654 user_content (FILE *in
, char *buf
, CT
*ctp
, const char *infilename
)
658 char buffer
[NMH_BUFSIZ
];
662 struct str2init
*s2i
;
667 if (buf
[0] == '\n' || (do_direct() && strcmp (buf
, "#\n") == 0)) {
672 /* allocate basic Content structure */
676 /* allocate basic structure for handling decoded content */
677 init_decoded_content (ct
, infilename
);
684 * Handle inline text. Check if line
685 * is one of the following forms:
687 * 1) doesn't begin with '#' (implicit directive)
688 * 2) begins with "##" (implicit directive)
689 * 3) begins with "#<"
691 if (!do_direct() || buf
[0] != '#' || buf
[1] == '#' || buf
[1] == '<') {
695 char content
[BUFSIZ
];
699 if ((cp
= m_mktemp2(NULL
, invo_name
, NULL
, &out
)) == NULL
) {
700 adios("mhbuildsbr", "unable to create temporary file in %s",
704 /* use a temp file to collect the plain text lines */
705 ce
->ce_file
= mh_xstrdup(cp
);
708 if (do_direct() && (buf
[0] == '#' && buf
[1] == '<')) {
709 strncpy (content
, buf
+ 2, sizeof(content
));
715 /* the directive is implicit */
716 strncpy (content
, "text/plain", sizeof(content
));
718 strncpy (buffer
, (!do_direct() || buf
[0] != '#') ? buf
: buf
+ 1, sizeof(buffer
));
722 if (headers
>= 0 && do_direct() && uprf (buffer
, DESCR_FIELD
)
723 && buffer
[i
= LEN(DESCR_FIELD
)] == ':') {
727 ct
->c_descr
= add (buffer
+ i
+ 1, ct
->c_descr
);
728 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
729 die("end-of-file after %s: field in plaintext", DESCR_FIELD
);
737 die("#-directive after %s: field in plaintext", DESCR_FIELD
);
745 if (headers
>= 0 && do_direct() && uprf (buffer
, DISPO_FIELD
)
746 && buffer
[i
= LEN(DISPO_FIELD
)] == ':') {
750 ct
->c_dispo
= add (buffer
+ i
+ 1, ct
->c_dispo
);
751 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
752 die("end-of-file after %s: field in plaintext", DISPO_FIELD
);
760 die("#-directive after %s: field in plaintext", DISPO_FIELD
);
768 if (headers
!= 1 || buffer
[0] != '\n')
774 if ((cp
= fgetstr (buffer
, sizeof(buffer
) - 1, in
)) == NULL
)
776 if (do_direct() && buffer
[0] == '#') {
779 if (buffer
[1] != '#')
781 for (cp
= (bp
= buffer
) + 1; *cp
; cp
++)
788 ct
->c_end
= ftell (out
);
791 /* parse content type */
792 if (get_ctinfo (content
, ct
, inlineD
) == NOTOK
)
795 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
796 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
798 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
802 * check type specified (possibly implicitly)
804 switch (ct
->c_type
= s2i
->si_val
) {
806 if (!strcasecmp (ci
->ci_subtype
, "rfc822")) {
807 ct
->c_encoding
= CE_7BIT
;
812 die("it doesn't make sense to define an in-line %s content",
813 ct
->c_type
== CT_MESSAGE
? "message" : "multipart");
818 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
819 (*ct
->c_ctinitfnx
) (ct
);
824 fseek (in
, pos
, SEEK_SET
);
829 * If we've reached this point, the next line
830 * must be some type of explicit directive.
833 /* check if directive is external-type */
834 extrnal
= (buf
[1] == '@');
836 /* parse directive */
837 if (get_ctinfo (buf
+ (extrnal
? 2 : 1), ct
, 1) == NOTOK
)
840 /* check directive against the list of MIME types */
841 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
842 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
846 * Check if the directive specified a valid type.
847 * This will happen if it was one of the following forms:
854 die("missing subtype in \"#%s\"", ci
->ci_type
);
856 switch (ct
->c_type
= s2i
->si_val
) {
858 die("use \"#begin ... #end\" instead of \"#%s/%s\"",
859 ci
->ci_type
, ci
->ci_subtype
);
863 if (!strcasecmp (ci
->ci_subtype
, "partial"))
864 die("sorry, \"#%s/%s\" isn't supported",
865 ci
->ci_type
, ci
->ci_subtype
);
866 if (!strcasecmp (ci
->ci_subtype
, "external-body"))
867 die("use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
868 ci
->ci_type
, ci
->ci_subtype
);
870 die( "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
871 ci
->ci_type
, ci
->ci_subtype
);
875 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
876 (*ct
->c_ctinitfnx
) (ct
);
881 * #@type/subtype (external types directive)
888 die("need external information for \"#@%s/%s\"",
889 ci
->ci_type
, ci
->ci_subtype
);
892 snprintf (buffer
, sizeof(buffer
), "message/external-body; %s", ci
->ci_magic
);
897 * Since we are using the current Content structure to
898 * hold information about the type of the external
899 * reference, we need to create another Content structure
900 * for the message/external-body to wrap it in.
903 init_decoded_content(ct
, infilename
);
905 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
907 ct
->c_type
= CT_MESSAGE
;
908 ct
->c_subtype
= MESSAGE_EXTERNAL
;
911 ct
->c_ctparams
= (void *) e
;
917 if (params_external (ct
, 1) == NOTOK
)
923 /* Handle [file] argument */
925 /* check if specifies command to execute */
926 if (*ci
->ci_magic
== '|' || *ci
->ci_magic
== '!') {
927 for (cp
= ci
->ci_magic
+ 1; isspace ((unsigned char) *cp
); cp
++)
930 die("empty pipe command for #%s directive", ci
->ci_type
);
935 /* record filename of decoded contents */
936 ce
->ce_file
= ci
->ci_magic
;
937 if (access (ce
->ce_file
, R_OK
) == NOTOK
)
938 adios ("reading", "unable to access %s for", ce
->ce_file
);
939 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
940 ct
->c_end
= (long) st
.st_size
;
947 * No [file] argument, so check profile for
948 * method to compose content.
950 cp
= context_find_by_type ("compose", ci
->ci_type
, ci
->ci_subtype
);
952 content_error (NULL
, ct
, "don't know how to compose content");
955 ci
->ci_magic
= mh_xstrdup(cp
);
960 die("external definition not allowed for \"#%s\"", ci
->ci_type
);
964 * #forw [+folder] [msgs]
966 if (!strcasecmp (ci
->ci_type
, "forw")) {
968 char *folder
, *arguments
[MAXARGS
];
972 ap
= brkstring (ci
->ci_magic
, " ", "\n");
973 copyip (ap
, arguments
, MAXARGS
);
975 arguments
[0] = "cur";
980 /* search the arguments for a folder name */
981 for (ap
= arguments
; *ap
; ap
++) {
983 if (*cp
== '+' || *cp
== '@') {
985 die("only one folder per #forw directive");
986 folder
= pluspath (cp
);
990 /* else, use the current folder */
992 folder
= mh_xstrdup(getfolder(1));
994 if (!(mp
= folder_read (folder
, 0)))
995 die("unable to read folder %s", folder
);
996 for (ap
= arguments
; *ap
; ap
++) {
998 if (*cp
!= '+' && *cp
!= '@')
999 if (!m_convert (mp
, cp
))
1006 * If there is more than one message to include, make this
1007 * a content of type "multipart/digest" and insert each message
1008 * as a subpart. If there is only one message, then make this
1009 * a content of type "message/rfc822".
1011 if (mp
->numsel
> 1) {
1012 /* we are forwarding multiple messages */
1013 if (get_ctinfo ("multipart/digest", ct
, 0) == NOTOK
)
1015 ct
->c_type
= CT_MULTIPART
;
1016 ct
->c_subtype
= MULTI_DIGEST
;
1019 ct
->c_ctparams
= (void *) m
;
1022 for (msgnum
= mp
->lowsel
; msgnum
<= mp
->hghsel
; msgnum
++) {
1023 if (is_selected(mp
, msgnum
)) {
1029 init_decoded_content (p
, infilename
);
1031 if (get_ctinfo ("message/rfc822", p
, 0) == NOTOK
)
1033 p
->c_type
= CT_MESSAGE
;
1034 p
->c_subtype
= MESSAGE_RFC822
;
1036 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1037 pe
->ce_file
= mh_xstrdup(buffer
);
1038 if (listsw
&& stat (pe
->ce_file
, &st
) != NOTOK
)
1039 p
->c_end
= (long) st
.st_size
;
1043 pp
= &part
->mp_next
;
1048 /* we are forwarding one message */
1049 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
1051 ct
->c_type
= CT_MESSAGE
;
1052 ct
->c_subtype
= MESSAGE_RFC822
;
1054 msgnum
= mp
->lowsel
;
1055 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1056 ce
->ce_file
= mh_xstrdup(buffer
);
1057 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
1058 ct
->c_end
= (long) st
.st_size
;
1061 folder_free (mp
); /* free folder/message structure */
1068 if (!strcasecmp (ci
->ci_type
, "end")) {
1075 * #begin [ alternative | parallel ]
1077 if (!strcasecmp (ci
->ci_type
, "begin")) {
1078 if (!ci
->ci_magic
) {
1080 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1081 } else if (!strcasecmp (ci
->ci_magic
, "alternative")) {
1082 vrsn
= MULTI_ALTERNATE
;
1083 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1084 } else if (!strcasecmp (ci
->ci_magic
, "parallel")) {
1085 vrsn
= MULTI_PARALLEL
;
1086 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1087 } else if (uprf (ci
->ci_magic
, "digest")) {
1090 vrsn
= MULTI_UNKNOWN
;
1095 snprintf (buffer
, sizeof(buffer
), "multipart/%s", cp
);
1096 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
1098 ct
->c_type
= CT_MULTIPART
;
1099 ct
->c_subtype
= vrsn
;
1102 ct
->c_ctparams
= (void *) m
;
1105 while (fgetstr (buffer
, sizeof(buffer
) - 1, in
)) {
1109 if (user_content (in
, buffer
, &p
, infilename
) == DONE
) {
1111 die("empty \"#begin ... #end\" sequence");
1119 pp
= &part
->mp_next
;
1122 inform("premature end-of-file, missing #end, continuing...");
1129 die("unknown directive \"#%s\"", ci
->ci_type
);
1130 return NOTOK
; /* NOT REACHED */
1135 set_id (CT ct
, int top
)
1137 char contentid
[BUFSIZ
];
1139 static time_t clock
= 0;
1140 static char *msgfmt
;
1144 snprintf (contentid
, sizeof(contentid
), "%s\n", message_id (clock
, 1));
1146 msgfmt
= mh_xstrdup(contentid
);
1148 snprintf (contentid
, sizeof(contentid
), msgfmt
, top
? 0 : ++partno
);
1149 ct
->c_id
= mh_xstrdup(contentid
);
1154 * Fill out, or expand the various contents in the composition
1155 * draft. Read-in any necessary files. Parse and execute any
1156 * commands specified by profile composition strings.
1160 compose_content (CT ct
, int verbose
)
1162 CE ce
= &ct
->c_cefile
;
1164 switch (ct
->c_type
) {
1169 char partnam
[BUFSIZ
];
1170 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1174 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1175 pp
= partnam
+ strlen (partnam
);
1180 /* first, we call compose_content on all the subparts */
1181 for (part
= m
->mp_parts
, partnum
= 1; part
; part
= part
->mp_next
, partnum
++) {
1182 CT p
= part
->mp_part
;
1184 sprintf (pp
, "%d", partnum
);
1185 p
->c_partno
= mh_xstrdup(partnam
);
1186 if (compose_content (p
, verbose
) == NOTOK
)
1191 * If the -rfc934mode switch is given, then check all
1192 * the subparts of a multipart/digest. If they are all
1193 * message/rfc822, then mark this content and all
1194 * subparts with the rfc934 compatibility mode flag.
1196 if (rfc934sw
&& ct
->c_subtype
== MULTI_DIGEST
) {
1199 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1200 CT p
= part
->mp_part
;
1202 if (p
->c_subtype
!= MESSAGE_RFC822
) {
1207 ct
->c_rfc934
= is934
;
1208 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1209 CT p
= part
->mp_part
;
1211 if ((p
->c_rfc934
= is934
))
1217 ct
->c_end
= (partnum
= strlen (prefix
) + 2) + 2;
1221 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
)
1222 ct
->c_end
+= part
->mp_part
->c_end
+ partnum
;
1228 /* Nothing to do for type message */
1232 * Discrete types (text/application/audio/image/video)
1240 char *vec
[4], buffer
[BUFSIZ
];
1242 CI ci
= &ct
->c_ctinfo
;
1245 if (!(cp
= ci
->ci_magic
))
1246 die("internal error(5)");
1248 if ((tfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1249 adios("mhbuildsbr", "unable to create temporary file in %s",
1252 ce
->ce_file
= mh_xstrdup(tfile
);
1257 /* Get buffer ready to go */
1260 buflen
= sizeof(buffer
);
1263 * Parse composition string into buffer
1265 for ( ; *cp
; cp
++) {
1270 /* insert parameters from directive */
1274 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1275 snprintf (bp
, buflen
, "%s%s=\"%s\"", s
,
1276 pm
->pm_name
, get_param_value(pm
, '?'));
1286 /* %f, and stdout is not-redirected */
1292 * insert temporary filename where
1293 * content should be written
1295 snprintf (bp
, buflen
, "%s", ce
->ce_file
);
1299 /* insert content subtype */
1300 strncpy (bp
, ci
->ci_subtype
, buflen
);
1304 /* insert character % */
1325 printf ("composing content %s/%s from command\n\t%s\n",
1326 ci
->ci_type
, ci
->ci_subtype
, buffer
);
1328 fflush (stdout
); /* not sure if need for -noverbose */
1335 if ((out
= fopen (ce
->ce_file
, "w")) == NULL
)
1336 adios (ce
->ce_file
, "unable to open for writing");
1341 adios ("fork", "unable to fork");
1346 dup2 (fileno (out
), 1);
1347 close (fileno (out
));
1348 execvp ("/bin/sh", vec
);
1349 fprintf (stderr
, "unable to exec ");
1356 if (pidXwait(child_id
, NULL
))
1362 /* Check size of file */
1363 if (listsw
&& ct
->c_end
== 0L) {
1366 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1367 ct
->c_end
= (long) st
.st_size
;
1379 * 1) choose a transfer encoding.
1380 * 2) check for clashes with multipart boundary string.
1381 * 3) for text content, figure out which character set is being used.
1383 * If there is a clash with one of the contents and the multipart boundary,
1384 * this function will exit with NOTOK. This will cause the scanning process
1385 * to be repeated with a different multipart boundary. It is possible
1386 * (although highly unlikely) that this scan will be repeated multiple times.
1390 scan_content (CT ct
, size_t maxunencoded
)
1393 bool check8bit
= false, contains8bit
= false; /* check if contains 8bit data */
1394 bool checknul
= false, containsnul
= false; /* check if contains NULs */
1395 bool checklinelen
= false, linelen
= false; /* check for long lines */
1396 bool checkllinelen
= false; /* check for extra-long lines */
1397 bool checkboundary
= false, boundaryclash
= false; /* check if clashes with multipart boundary */
1398 bool checklinespace
= false, linespace
= false; /* check if any line ends with space */
1403 struct text
*t
= NULL
;
1405 CE ce
= &ct
->c_cefile
;
1408 * handle multipart by scanning all subparts
1409 * and then checking their encoding.
1411 if (ct
->c_type
== CT_MULTIPART
) {
1412 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1415 /* initially mark the domain of enclosing multipart as 7bit */
1416 ct
->c_encoding
= CE_7BIT
;
1418 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1419 CT p
= part
->mp_part
;
1421 if (scan_content (p
, maxunencoded
) == NOTOK
) /* choose encoding for subpart */
1424 /* if necessary, enlarge encoding for enclosing multipart */
1425 if (p
->c_encoding
== CE_BINARY
)
1426 ct
->c_encoding
= CE_BINARY
;
1427 if (p
->c_encoding
== CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
)
1428 ct
->c_encoding
= CE_8BIT
;
1435 * Decide what to check while scanning this content. Note that
1436 * for text content we always check for 8bit characters if the
1437 * charset is unspecified, because that controls whether or not the
1438 * character set is us-ascii or retrieved from the locale. And
1439 * we check even if the charset is specified, to allow setting
1440 * the proper Content-Transfer-Encoding.
1443 if (ct
->c_type
== CT_TEXT
) {
1444 t
= (struct text
*) ct
->c_ctparams
;
1445 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
1451 switch (ct
->c_reqencoding
) {
1453 checkllinelen
= true;
1454 checkboundary
= true;
1457 checkboundary
= true;
1462 /* Use the default rules based on content-type */
1463 switch (ct
->c_type
) {
1465 checkboundary
= true;
1466 checklinelen
= true;
1467 if (ct
->c_subtype
== TEXT_PLAIN
) {
1468 checklinespace
= false;
1470 checklinespace
= true;
1474 case CT_APPLICATION
:
1477 checklinelen
= true;
1478 checklinespace
= true;
1479 checkboundary
= true;
1483 checklinelen
= false;
1484 checklinespace
= false;
1486 /* don't check anything for message/external */
1487 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1488 checkboundary
= false;
1491 checkboundary
= true;
1500 * Don't check anything for these types,
1501 * since we are forcing use of base64, unless
1502 * the content-type was specified by a mhbuild directive.
1505 checklinelen
= false;
1506 checklinespace
= false;
1507 checkboundary
= false;
1513 * Scan the unencoded content
1515 if (check8bit
|| checklinelen
|| checklinespace
|| checkboundary
||
1516 checkllinelen
|| checknul
) {
1517 if ((in
= fopen (ce
->ce_file
, "r")) == NULL
)
1518 adios (ce
->ce_file
, "unable to open for reading");
1519 prefix_len
= strlen (prefix
);
1521 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1523 * Check for 8bit and NUL data.
1525 for (cp
= bufp
; (check8bit
|| checknul
) &&
1526 cp
< bufp
+ gotlen
; cp
++) {
1527 if (!isascii ((unsigned char) *cp
)) {
1528 contains8bit
= true;
1529 check8bit
= false; /* no need to keep checking */
1533 checknul
= false; /* no need to keep checking */
1538 * Check line length.
1540 if (checklinelen
&& ((size_t)gotlen
> maxunencoded
+ 1)) {
1542 checklinelen
= false; /* no need to keep checking */
1546 * RFC 5322 specifies that a message cannot contain a line
1547 * greater than 998 characters (excluding the CRLF). If we
1548 * get one of those lines and linelen is NOT set, then abort.
1551 if (checkllinelen
&& !linelen
&&
1552 (gotlen
> MAXLONGLINE
+ 1)) {
1553 die("Line in content exceeds maximum line limit (%d)",
1558 * Check if line ends with a space.
1560 if (checklinespace
&& (cp
= bufp
+ gotlen
- 2) > bufp
&&
1561 isspace ((unsigned char) *cp
)) {
1563 checklinespace
= false; /* no need to keep checking */
1567 * Check if content contains a line that clashes
1568 * with our standard boundary for multipart messages.
1570 if (checkboundary
&& bufp
[0] == '-' && bufp
[1] == '-') {
1571 for (cp
= bufp
+ gotlen
- 1; cp
>= bufp
; cp
--)
1572 if (!isspace ((unsigned char) *cp
))
1575 if (!strncmp(bufp
+ 2, prefix
, prefix_len
) &&
1576 isdigit((unsigned char) bufp
[2 + prefix_len
])) {
1577 boundaryclash
= true;
1578 checkboundary
= false; /* no need to keep checking */
1587 * If the content is text and didn't specify a character set,
1588 * we need to figure out which one was used.
1590 set_charset (ct
, contains8bit
);
1593 * Decide which transfer encoding to use.
1596 if (ct
->c_reqencoding
!= CE_UNKNOWN
)
1597 ct
->c_encoding
= ct
->c_reqencoding
;
1599 int wants_q_p
= (containsnul
|| linelen
|| linespace
|| checksw
);
1601 switch (ct
->c_type
) {
1604 ct
->c_encoding
= CE_QUOTED
;
1605 else if (contains8bit
)
1606 ct
->c_encoding
= CE_8BIT
;
1608 ct
->c_encoding
= CE_7BIT
;
1612 case CT_APPLICATION
:
1613 /* For application type, use base64, except when postscript */
1614 if (wants_q_p
|| contains8bit
) {
1615 if (ct
->c_subtype
== APPLICATION_POSTSCRIPT
)
1616 ct
->c_encoding
= CE_QUOTED
; /* historical */
1618 ct
->c_encoding
= CE_BASE64
;
1620 ct
->c_encoding
= CE_7BIT
;
1625 ct
->c_encoding
= contains8bit
? CE_8BIT
: CE_7BIT
;
1631 /* For audio, image, and video contents, just use base64 */
1632 ct
->c_encoding
= CE_BASE64
;
1637 return boundaryclash
? NOTOK
: OK
;
1642 * Scan the content structures, and build header
1643 * fields that will need to be output into the
1648 build_headers (CT ct
, int header_encoding
)
1650 int cc
, mailbody
, extbody
, len
;
1651 char *np
, *vp
, buffer
[BUFSIZ
];
1652 CI ci
= &ct
->c_ctinfo
;
1655 * If message is type multipart, then add the multipart
1656 * boundary to the list of attribute/value pairs.
1658 if (ct
->c_type
== CT_MULTIPART
) {
1659 static int level
= 0; /* store nesting level */
1661 snprintf (buffer
, sizeof(buffer
), "%s%d", prefix
, level
++);
1662 add_param(&ci
->ci_first_pm
, &ci
->ci_last_pm
, "boundary", buffer
, 0);
1666 * Skip the output of Content-Type, parameters, content
1667 * description and disposition, and Content-ID if the
1668 * content is of type "message" and the rfc934 compatibility
1669 * flag is set (which means we are inside multipart/digest
1670 * and the switch -rfc934mode was given).
1672 if (ct
->c_type
== CT_MESSAGE
&& ct
->c_rfc934
)
1676 * output the content type and subtype
1678 np
= mh_xstrdup(TYPE_FIELD
);
1679 vp
= concat (" ", ci
->ci_type
, "/", ci
->ci_subtype
, NULL
);
1681 /* keep track of length of line */
1682 len
= LEN(TYPE_FIELD
) + strlen (ci
->ci_type
)
1683 + strlen (ci
->ci_subtype
) + 3;
1685 extbody
= ct
->c_type
== CT_MESSAGE
&& ct
->c_subtype
== MESSAGE_EXTERNAL
;
1686 mailbody
= extbody
&& ((struct exbody
*) ct
->c_ctparams
)->eb_body
;
1689 * Append the attribute/value pairs to
1690 * the end of the Content-Type line.
1693 if (ci
->ci_first_pm
) {
1694 char *s
= output_params(len
, ci
->ci_first_pm
, &len
, mailbody
);
1697 die("Internal error: failed outputting Content-Type "
1705 * Append any RFC-822 comment to the end of
1706 * the Content-Type line.
1708 if (ci
->ci_comment
) {
1709 snprintf (buffer
, sizeof(buffer
), "(%s)", ci
->ci_comment
);
1710 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
1711 vp
= add ("\n\t", vp
);
1717 vp
= add (buffer
, vp
);
1720 vp
= add ("\n", vp
);
1721 add_header (ct
, np
, vp
);
1724 * output the Content-ID, unless disabled by -nocontentid. Note that
1725 * RFC 2045 always requires a Content-ID header for message/external-body
1728 if ((contentidsw
|| ct
->c_ctexbody
) && ct
->c_id
) {
1729 np
= mh_xstrdup(ID_FIELD
);
1730 vp
= concat (" ", ct
->c_id
, NULL
);
1731 add_header (ct
, np
, vp
);
1734 * output the Content-Description
1737 np
= mh_xstrdup(DESCR_FIELD
);
1738 vp
= concat (" ", ct
->c_descr
, NULL
);
1739 if (header_encoding
!= CE_8BIT
) {
1740 if (encode_rfc2047(DESCR_FIELD
, &vp
, header_encoding
, NULL
)) {
1741 die("Unable to encode %s header", DESCR_FIELD
);
1744 add_header (ct
, np
, vp
);
1748 * output the Content-Disposition. If it's NULL but c_dispo_type is
1749 * set, then we need to build it.
1752 np
= mh_xstrdup(DISPO_FIELD
);
1753 vp
= concat (" ", ct
->c_dispo
, NULL
);
1754 add_header (ct
, np
, vp
);
1755 } else if (ct
->c_dispo_type
) {
1756 vp
= concat (" ", ct
->c_dispo_type
, NULL
);
1757 len
= LEN(DISPO_FIELD
) + strlen(vp
) + 1;
1758 np
= output_params(len
, ct
->c_dispo_first
, NULL
, 0);
1762 add_header (ct
, mh_xstrdup(DISPO_FIELD
), vp
);
1767 * If this is the internal content structure for a
1768 * "message/external", then we are done with the
1769 * headers (since it has no body).
1775 * output the Content-MD5
1778 np
= mh_xstrdup(MD5_FIELD
);
1779 vp
= calculate_digest (ct
, ct
->c_encoding
== CE_QUOTED
);
1780 add_header (ct
, np
, vp
);
1784 * output the Content-Transfer-Encoding
1785 * If using EAI and message body is 7-bit, force 8-bit C-T-E.
1787 if (header_encoding
== CE_8BIT
&& ct
->c_encoding
== CE_7BIT
) {
1788 ct
->c_encoding
= CE_8BIT
;
1791 switch (ct
->c_encoding
) {
1793 /* Nothing to output */
1797 np
= mh_xstrdup(ENCODING_FIELD
);
1798 vp
= concat (" ", "8bit", "\n", NULL
);
1799 add_header (ct
, np
, vp
);
1803 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1804 die("internal error, invalid encoding");
1806 np
= mh_xstrdup(ENCODING_FIELD
);
1807 vp
= concat (" ", "quoted-printable", "\n", NULL
);
1808 add_header (ct
, np
, vp
);
1812 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1813 die("internal error, invalid encoding");
1815 np
= mh_xstrdup(ENCODING_FIELD
);
1816 vp
= concat (" ", "base64", "\n", NULL
);
1817 add_header (ct
, np
, vp
);
1821 if (ct
->c_type
== CT_MESSAGE
)
1822 die("internal error, invalid encoding");
1824 np
= mh_xstrdup(ENCODING_FIELD
);
1825 vp
= concat (" ", "binary", "\n", NULL
);
1826 add_header (ct
, np
, vp
);
1830 die("unknown transfer encoding in content");
1835 * Additional content specific header processing
1837 switch (ct
->c_type
) {
1840 struct multipart
*m
;
1843 m
= (struct multipart
*) ct
->c_ctparams
;
1844 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1848 build_headers (p
, header_encoding
);
1854 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1857 e
= (struct exbody
*) ct
->c_ctparams
;
1858 build_headers (e
->eb_content
, header_encoding
);
1871 static char nib2b64
[0x40+1] =
1872 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1875 calculate_digest (CT ct
, int asciiP
)
1880 unsigned char digest
[16];
1881 unsigned char outbuf
[25];
1883 CE ce
= &ct
->c_cefile
;
1884 char *infilename
= ce
->ce_file
? ce
->ce_file
: ct
->c_file
;
1888 if ((in
= fopen (infilename
, "r")) == NULL
)
1889 adios (infilename
, "unable to open for reading");
1891 /* Initialize md5 context */
1892 MD5Init (&mdContext
);
1894 /* calculate md5 message digest */
1899 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1902 cp
= bufp
+ gotlen
- 1;
1903 if ((c
= *cp
) == '\n')
1906 MD5Update (&mdContext
, (unsigned char *) bufp
,
1907 (unsigned int) gotlen
);
1910 MD5Update (&mdContext
, (unsigned char *) "\r\n", 2);
1913 char buffer
[BUFSIZ
];
1914 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), in
)) > 0)
1915 MD5Update (&mdContext
, (unsigned char *) buffer
, (unsigned int) cc
);
1918 /* md5 finalization. Write digest and zero md5 context */
1919 MD5Final (digest
, &mdContext
);
1924 /* print debugging info */
1928 fprintf (stderr
, "MD5 digest=");
1929 for (ep
= (dp
= digest
) + sizeof digest
;
1931 fprintf (stderr
, "%02x", *dp
& 0xff);
1932 fprintf (stderr
, "\n");
1935 /* encode the digest using base64 */
1936 for (dp
= digest
, op
= (char *) outbuf
,
1938 cc
> 0; cc
-= 3, op
+= 4) {
1942 bits
= (*dp
++ & 0xff) << 16;
1944 bits
|= (*dp
++ & 0xff) << 8;
1946 bits
|= *dp
++ & 0xff;
1949 for (bp
= op
+ 4; bp
> op
; bits
>>= 6)
1950 *--bp
= nib2b64
[bits
& 0x3f];
1958 /* null terminate string */
1961 /* now make copy and return string */
1962 vp
= concat (" ", outbuf
, "\n", NULL
);
1967 * Set things up for the content structure for file "filename" that
1972 setup_attach_content(CT ct
, char *filename
)
1974 char *type
, *simplename
= r1bindex(filename
, '/');
1975 struct str2init
*s2i
;
1978 if (! (type
= mime_type(filename
))) {
1979 die("Unable to determine MIME type of \"%s\"", filename
);
1983 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1984 * since we're just feeding it a MIME type we have to add those ourselves.
1985 * Map that to a valid content-type label and call any initialization
1989 if (get_ctinfo(type
, ct
, 0) == NOTOK
)
1994 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
1995 if (strcasecmp(ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0)
1997 if (!s2i
->si_key
&& !uprf(ct
->c_ctinfo
.ci_type
, "X-"))
2001 * Make sure the type isn't incompatible with what we can handle
2004 switch (ct
->c_type
= s2i
->si_val
) {
2006 die("multipart types must be specified by mhbuild directives");
2010 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "partial") == 0)
2011 die("Sorry, %s/%s isn't supported", ct
->c_ctinfo
.ci_type
,
2012 ct
->c_ctinfo
.ci_subtype
);
2013 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "external-body") == 0)
2014 die("external-body messages must be specified "
2015 "by mhbuild directives");
2020 * This sets the subtype, if it's significant
2022 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
2023 (*ct
->c_ctinitfnx
)(ct
);
2028 * Feed in a few attributes; specifically, the name attribute, the
2029 * content-description, and the content-disposition.
2032 for (pm
= ct
->c_ctinfo
.ci_first_pm
; pm
; pm
= pm
->pm_next
) {
2033 if (strcasecmp(pm
->pm_name
, "name") == 0) {
2035 pm
->pm_value
= mh_xstrdup(simplename
);
2041 add_param(&ct
->c_ctinfo
.ci_first_pm
, &ct
->c_ctinfo
.ci_last_pm
,
2042 "name", simplename
, 0);
2044 ct
->c_descr
= mh_xstrdup(simplename
);
2045 ct
->c_descr
= add("\n", ct
->c_descr
);
2046 ct
->c_cefile
.ce_file
= mh_xstrdup(filename
);
2048 set_disposition (ct
);
2050 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename", simplename
, 0);
2054 * If disposition type hasn't already been set in ct:
2055 * Look for mhbuild-disposition-<type>/<subtype> entry
2056 * that specifies Content-Disposition type. Only
2057 * 'attachment' and 'inline' are allowed. Default to
2061 set_disposition (CT ct
)
2063 if (ct
->c_dispo_type
== NULL
) {
2064 char *cp
= context_find_by_type ("disposition", ct
->c_ctinfo
.ci_type
,
2065 ct
->c_ctinfo
.ci_subtype
);
2067 if (cp
&& strcasecmp (cp
, "attachment") &&
2068 strcasecmp (cp
, "inline")) {
2069 inform("configuration problem: %s-disposition-%s%s%s specifies "
2070 "'%s' but only 'attachment' and 'inline' are allowed, "
2071 "continuing...", invo_name
,
2072 ct
->c_ctinfo
.ci_type
,
2073 ct
->c_ctinfo
.ci_subtype
? "/" : "",
2074 FENDNULL(ct
->c_ctinfo
.ci_subtype
),
2080 ct
->c_dispo_type
= mh_xstrdup(cp
);
2085 * Set text content charset if it was unspecified. contains8bit
2087 * 0: content does not contain 8-bit characters
2088 * 1: content contains 8-bit characters
2089 * -1: ignore content and use user's locale to determine charset
2092 set_charset (CT ct
, int contains8bit
)
2094 if (ct
->c_type
== CT_TEXT
) {
2097 if (ct
->c_ctparams
== NULL
) {
2100 t
->tx_charset
= CHARSET_UNSPECIFIED
;
2102 t
= (struct text
*) ct
->c_ctparams
;
2105 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
2106 CI ci
= &ct
->c_ctinfo
;
2107 char *eightbitcharset
= write_charset_8bit();
2108 char *charset
= contains8bit
? eightbitcharset
: "us-ascii";
2110 if (contains8bit
== 1 &&
2111 strcasecmp (eightbitcharset
, "US-ASCII") == 0) {
2112 die("Text content contains 8 bit characters, but "
2113 "character set is US-ASCII");
2116 add_param (&ci
->ci_first_pm
, &ci
->ci_last_pm
, "charset", charset
,
2119 t
->tx_charset
= CHARSET_SPECIFIED
;
2126 * Look at all of the replied-to message parts and expand any that
2127 * are matched by a pseudoheader. Except don't descend into
2131 expand_pseudoheaders (CT ct
, struct multipart
*m
, const char *infile
,
2132 const convert_list
*convert_head
)
2134 /* text_plain_ct is used to concatenate all of the text/plain
2135 replies into one part, instead of having each one in a separate
2137 CT text_plain_ct
= NULL
;
2139 switch (ct
->c_type
) {
2140 case CT_MULTIPART
: {
2141 struct multipart
*mp
= (struct multipart
*) ct
->c_ctparams
;
2144 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
2145 bool matched
= false;
2147 /* The parts are in descending priority order (defined by
2148 RFC 2046 Sec. 5.1.4) because they were reversed by
2149 parse_mime (). So, stop looking for matches with
2150 immediate subparts after the first match of an
2152 for (part
= mp
->mp_parts
; ! matched
&& part
; part
= part
->mp_next
) {
2153 char *type_subtype
=
2154 concat (part
->mp_part
->c_ctinfo
.ci_type
, "/",
2155 part
->mp_part
->c_ctinfo
.ci_subtype
, NULL
);
2157 if (part
->mp_part
->c_type
== CT_MULTIPART
) {
2158 expand_pseudoheaders (part
->mp_part
, m
, infile
,
2161 const convert_list
*c
;
2163 for (c
= convert_head
; c
; c
= c
->next
) {
2164 if (! strcasecmp (type_subtype
, c
->type
)) {
2165 expand_pseudoheader (part
->mp_part
, &text_plain_ct
,
2167 c
->type
, c
->argstring
);
2173 free (type_subtype
);
2176 for (part
= mp
->mp_parts
; part
; part
= part
->mp_next
) {
2177 expand_pseudoheaders (part
->mp_part
, m
, infile
, convert_head
);
2184 char *type_subtype
=
2185 concat (ct
->c_ctinfo
.ci_type
, "/", ct
->c_ctinfo
.ci_subtype
,
2187 const convert_list
*c
;
2189 for (c
= convert_head
; c
; c
= c
->next
) {
2190 if (! strcasecmp (type_subtype
, c
->type
)) {
2191 expand_pseudoheader (ct
, &text_plain_ct
, m
, infile
, c
->type
,
2196 free (type_subtype
);
2204 * Expand a single pseudoheader. It's for the specified type.
2207 expand_pseudoheader (CT ct
, CT
*text_plain_ct
, struct multipart
*m
,
2208 const char *infile
, const char *type
,
2209 const char *argstring
)
2212 FILE *reply_fp
= NULL
;
2213 char *convert
, *type_p
, *subtype_p
;
2214 char *convert_command
;
2215 char *charset
= NULL
;
2217 struct str2init
*s2i
;
2222 type_p
= getcpy (type
);
2223 if ((subtype_p
= strchr (type_p
, '/'))) {
2224 *subtype_p
++ = '\0';
2225 convert
= context_find_by_type ("convert", type_p
, subtype_p
);
2228 type_p
= concat ("mhbuild-convert-", type
, NULL
);
2229 convert
= context_find (type_p
);
2234 /* No mhbuild-convert- entry in mhn.defaults or profile for type. */
2237 /* reply_file is used to pass the output of the convert. */
2238 reply_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2240 concat (convert
, " ", FENDNULL(argstring
), " >", reply_file
, NULL
);
2242 /* Convert here . . . */
2243 ct
->c_storeproc
= mh_xstrdup(convert_command
);
2244 ct
->c_umask
= ~m_gmprot ();
2246 if ((status
= show_content_aux (ct
, 0, convert_command
, NULL
, NULL
)) !=
2248 inform("store of %s content failed, continuing...", type
);
2250 free (convert_command
);
2252 /* Fill out the the new ct, reply_ct. */
2254 init_decoded_content (reply_ct
, infile
);
2256 if (extract_headers (reply_ct
, reply_file
, &reply_fp
) == NOTOK
) {
2257 inform("failed to extract headers from convert output in %s, "
2258 "continuing...", reply_file
);
2263 /* This sets reply_ct->c_ctparams, and reply_ct->c_termproc if the
2264 charset can't be handled natively. */
2265 for (s2i
= str2cts
; s2i
->si_key
; s2i
++) {
2266 if (strcasecmp(reply_ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0) {
2271 if ((reply_ct
->c_ctinitfnx
= s2i
->si_init
)) {
2272 (*reply_ct
->c_ctinitfnx
)(reply_ct
);
2275 if ((cp
= get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1))) {
2276 /* The reply Content-Type had the charset. */
2279 set_charset (reply_ct
, -1);
2280 charset
= get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1);
2283 /* Concatenate text/plain parts. */
2284 if (reply_ct
->c_type
== CT_TEXT
&& reply_ct
->c_subtype
== TEXT_PLAIN
) {
2285 if (! *text_plain_ct
&& m
->mp_parts
&& m
->mp_parts
->mp_part
&&
2286 m
->mp_parts
->mp_part
->c_type
== CT_TEXT
&&
2287 m
->mp_parts
->mp_part
->c_subtype
== TEXT_PLAIN
) {
2288 *text_plain_ct
= m
->mp_parts
->mp_part
;
2289 /* Make sure that the charset is set in the text/plain part. */
2290 set_charset (*text_plain_ct
, -1);
2293 if (*text_plain_ct
) {
2294 /* Only concatenate if the charsets are identical. */
2295 char *text_plain_ct_charset
=
2296 get_param ((*text_plain_ct
)->c_ctinfo
.ci_first_pm
, "charset",
2299 if (strcasecmp (text_plain_ct_charset
, charset
) == 0) {
2300 /* Append this text/plain reply to the first one.
2301 If there's a problem anywhere along the way,
2302 instead attach it is a separate part. */
2303 int text_plain_reply
=
2304 open ((*text_plain_ct
)->c_cefile
.ce_file
,
2305 O_WRONLY
| O_APPEND
);
2306 int addl_reply
= open (reply_file
, O_RDONLY
);
2308 if (text_plain_reply
!= NOTOK
&& addl_reply
!= NOTOK
) {
2309 /* Insert blank line before each addl part. */
2310 /* It would be nice not to do this for the first one. */
2311 if (write (text_plain_reply
, "\n", 1) == 1) {
2312 /* Copy the text from the new reply and
2313 then free its Content struct. */
2314 cpydata (addl_reply
, text_plain_reply
,
2315 (*text_plain_ct
)->c_cefile
.ce_file
,
2317 if (close (text_plain_reply
) == OK
&&
2318 close (addl_reply
) == OK
) {
2319 /* If appended text needed 8-bit but first text didn't,
2320 propagate the 8-bit indication. */
2321 if ((*text_plain_ct
)->c_reqencoding
== CE_7BIT
&&
2322 reply_ct
->c_reqencoding
== CE_8BIT
) {
2323 (*text_plain_ct
)->c_reqencoding
= CE_8BIT
;
2326 if (reply_fp
) { fclose (reply_fp
); }
2328 free_content (reply_ct
);
2335 *text_plain_ct
= reply_ct
;
2339 reply_ct
->c_cefile
.ce_file
= reply_file
;
2340 reply_ct
->c_cefile
.ce_fp
= reply_fp
;
2341 reply_ct
->c_cefile
.ce_unlink
= 1;
2343 /* Attach the new part to the parent multipart/mixed, "m". */
2345 part
->mp_part
= reply_ct
;
2349 for (p
= m
->mp_parts
; p
&& p
->mp_next
; p
= p
->mp_next
) { continue; }
2357 /* Extract any Content-Type header from beginning of convert output. */
2359 extract_headers (CT ct
, char *reply_file
, FILE **reply_fp
)
2361 char *buffer
= NULL
, *cp
, *end_of_header
;
2362 bool found_header
= false;
2363 struct stat statbuf
;
2365 /* Read the convert reply from the file to memory. */
2366 if (stat (reply_file
, &statbuf
) == NOTOK
) {
2367 admonish (reply_file
, "failed to stat");
2368 goto failed_to_extract_ct
;
2371 buffer
= mh_xmalloc (statbuf
.st_size
+ 1);
2373 if ((*reply_fp
= fopen (reply_file
, "r+")) == NULL
||
2374 fread (buffer
, 1, (size_t) statbuf
.st_size
, *reply_fp
) <
2375 (size_t) statbuf
.st_size
) {
2376 admonish (reply_file
, "failed to read");
2377 goto failed_to_extract_ct
;
2379 buffer
[statbuf
.st_size
] = '\0';
2381 /* Look for a header in the convert reply. */
2382 if (strncasecmp (buffer
, TYPE_FIELD
, LEN(TYPE_FIELD
)) == 0 &&
2383 buffer
[LEN(TYPE_FIELD
)] == ':') {
2384 if ((end_of_header
= strstr (buffer
, "\r\n\r\n"))) {
2386 found_header
= true;
2387 } else if ((end_of_header
= strstr (buffer
, "\n\n"))) {
2389 found_header
= true;
2399 /* Truncate buffer to just the C-T. */
2400 *end_of_header
= '\0';
2401 n
= strlen (buffer
);
2403 if (get_ctinfo (buffer
+ 14, ct
, 0) != OK
) {
2404 inform("unable to get content info for reply, continuing...");
2405 goto failed_to_extract_ct
;
2408 /* Hack. Use parse_mime() to detect the type/subtype of the
2409 reply, which we'll use below. */
2410 tmp_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2411 tmp_f
= fopen(tmp_file
, "w");
2413 goto failed_to_extract_ct
;
2414 written
= fwrite(buffer
, 1, n
, tmp_f
);
2417 goto failed_to_extract_ct
;
2419 tmp_ct
= parse_mime (tmp_file
);
2421 /* The type and subtype were detected from the reply
2422 using parse_mime() above. */
2423 ct
->c_type
= tmp_ct
->c_type
;
2424 ct
->c_subtype
= tmp_ct
->c_subtype
;
2425 free_content (tmp_ct
);
2430 /* Rewrite the content without the header. */
2431 cp
= end_of_header
+ 1;
2434 if (fwrite (cp
, 1, statbuf
.st_size
- (cp
- buffer
), *reply_fp
) <
2435 (size_t) (statbuf
.st_size
- (cp
- buffer
))) {
2436 admonish (reply_file
, "failed to write");
2437 goto failed_to_extract_ct
;
2440 if (ftruncate (fileno (*reply_fp
), statbuf
.st_size
- (cp
- buffer
)) !=
2442 advise (reply_file
, "ftruncate");
2443 goto failed_to_extract_ct
;
2446 /* No header section, assume the reply is text/plain. */
2447 ct
->c_type
= CT_TEXT
;
2448 ct
->c_subtype
= TEXT_PLAIN
;
2449 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
) {
2450 /* This never should fail, but just in case. */
2451 die("unable to get content info for reply");
2455 /* free_encoding() will close reply_fp, which is passed through
2456 ct->c_cefile.ce_fp. */
2460 failed_to_extract_ct
:
2461 if (*reply_fp
) { fclose (*reply_fp
); }