3 * mhbuildsbr.c -- routines to expand/translate MIME composition files
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.
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.
24 #include <h/mhparse.h>
27 #ifdef HAVE_SYS_TIME_H
28 # include <sys/time.h>
37 extern int contentidsw
;
40 extern int rcachesw
; /* mhcachesbr.c */
41 extern int wcachesw
; /* mhcachesbr.c */
43 static char prefix
[] = "----- =_aaaaaaaaaa";
47 struct attach_list
*next
;
50 typedef struct convert_list
{
54 struct convert_list
*next
;
58 * Maximum size of URL token in message/external-body
61 #define MAXURLTOKEN 40
65 void content_error (char *, CT
, char *, ...);
68 int find_cache (CT
, int, int *, char *, char *, int);
72 void freects_done (int) NORETURN
;
73 void free_ctinfo (CT
);
74 void free_encoding (CT
, int);
79 static int init_decoded_content (CT
, const char *);
80 static void setup_attach_content(CT
, char *);
81 static void set_disposition (CT
);
82 static void set_charset (CT
, int);
83 static void expand_pseudoheaders (CT
, struct multipart
*, const char *,
84 const convert_list
*);
85 static void expand_pseudoheader (CT
, CT
*, struct multipart
*, const char *,
86 const char *, const char *);
87 static char *fgetstr (char *, int, FILE *);
88 static int user_content (FILE *, char *, CT
*, const char *infilename
);
89 static void set_id (CT
, int);
90 static int compose_content (CT
, int);
91 static int scan_content (CT
, size_t);
92 static int build_headers (CT
, int);
93 static char *calculate_digest (CT
, int);
94 static int extract_headers (CT
, char *, FILE **);
97 static unsigned char directives_stack
[32];
98 static unsigned int directives_index
;
100 static int do_direct(void)
102 return directives_stack
[directives_index
];
105 static void directive_onoff(int onoff
)
107 if (directives_index
>= sizeof(directives_stack
) - 1) {
108 fprintf(stderr
, "mhbuild: #on/off overflow, continuing\n");
111 directives_stack
[++directives_index
] = onoff
;
114 static void directive_init(int onoff
)
116 directives_index
= 0;
117 directives_stack
[0] = onoff
;
120 static void directive_pop(void)
122 if (directives_index
> 0)
125 fprintf(stderr
, "mhbuild: #pop underflow, continuing\n");
129 * Main routine for translating composition file
130 * into valid MIME message. It translates the draft
131 * into a content structure (actually a tree of content
132 * structures). This message then can be manipulated
133 * in various ways, including being output via
138 build_mime (char *infile
, int autobuild
, int dist
, int directives
,
139 int header_encoding
, size_t maxunencoded
, int verbose
)
142 char buf
[BUFSIZ
], name
[NAMESZ
];
149 m_getfld_state_t gstate
= 0;
150 struct attach_list
*attach_head
= NULL
, *attach_tail
= NULL
, *at_entry
;
151 convert_list
*convert_head
= NULL
, *convert_tail
= NULL
, *convert
;
153 directive_init(directives
);
155 umask (~m_gmprot ());
157 /* open the composition draft */
158 if ((in
= fopen (infile
, "r")) == NULL
)
159 adios (infile
, "unable to open for reading");
162 * Allocate space for primary (outside) content
167 * Allocate structure for handling decoded content
168 * for this part. We don't really need this, but
169 * allocate it to remain consistent.
171 init_decoded_content (ct
, infile
);
174 * Parse some of the header fields in the composition
175 * draft into the linked list of header fields for
176 * the new MIME message.
178 m_getfld_track_filepos (&gstate
, in
);
179 for (compnum
= 1;;) {
180 int bufsz
= sizeof buf
;
181 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
186 /* abort if draft has Mime-Version or C-T-E header field */
187 if (strcasecmp (name
, VRSN_FIELD
) == 0 ||
188 strcasecmp (name
, ENCODING_FIELD
) == 0) {
194 adios (NULL
, "draft shouldn't contain %s: field", name
);
197 /* ignore any Content-Type fields in the header */
198 if (!strcasecmp (name
, TYPE_FIELD
)) {
199 while (state
== FLDPLUS
) {
201 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
206 /* get copies of the buffers */
207 np
= mh_xstrdup(name
);
208 vp
= mh_xstrdup(buf
);
210 /* if necessary, get rest of field */
211 while (state
== FLDPLUS
) {
213 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
214 vp
= add (buf
, vp
); /* add to previous value */
218 * Now add the header data to the list, unless it's an attach
219 * header; in that case, add it to our attach list
222 if (strcasecmp(ATTACH_FIELD
, np
) == 0 ||
223 strcasecmp(ATTACH_FIELD_ALT
, np
) == 0) {
224 struct attach_list
*entry
;
225 char *s
= vp
, *e
= vp
+ strlen(vp
) - 1;
229 * Make sure we can find the start of this filename.
230 * If it's blank, we skip completely. Otherwise, strip
231 * off any leading spaces and trailing newlines.
234 while (isspace((unsigned char) *s
))
237 while (e
> s
&& *e
== '\n')
246 entry
->filename
= mh_xstrdup(s
);
251 attach_tail
->next
= entry
;
254 attach_head
= attach_tail
= entry
;
256 } else if (strncasecmp(MHBUILD_FILE_PSEUDOHEADER
, np
,
257 strlen (MHBUILD_FILE_PSEUDOHEADER
)) == 0) {
259 * Nmh-mhbuild-file-text/calendar: /home/user/Mail/inbox/9
261 char *type
= np
+ strlen (MHBUILD_FILE_PSEUDOHEADER
);
264 /* vp should begin with a space because m_getfld()
265 includes the space after the colon in buf. */
266 while (isspace((unsigned char) *filename
)) { ++filename
; }
267 /* Trim trailing newline and any other whitespace. */
270 for (convert
= convert_head
; convert
; convert
= convert
->next
) {
271 if (strcasecmp (convert
->type
, type
) == 0) { break; }
274 if (convert
->filename
&&
275 strcasecmp (convert
->filename
, filename
)) {
276 adios (NULL
, "Multiple %s headers with different files"
277 " not allowed", type
);
279 convert
->filename
= mh_xstrdup(filename
);
283 convert
->filename
= mh_xstrdup(filename
);
284 convert
->type
= mh_xstrdup(type
);
287 convert_tail
->next
= convert
;
289 convert_head
= convert
;
291 convert_tail
= convert
;
296 } else if (strncasecmp(MHBUILD_ARGS_PSEUDOHEADER
, np
,
297 strlen (MHBUILD_ARGS_PSEUDOHEADER
)) == 0) {
299 * Nmh-mhbuild-args-text/calendar: -reply accept
301 char *type
= np
+ strlen (MHBUILD_ARGS_PSEUDOHEADER
);
302 char *argstring
= vp
;
304 /* vp should begin with a space because m_getfld()
305 includes the space after the colon in buf. */
306 while (isspace((unsigned char) *argstring
)) { ++argstring
; }
307 /* Trim trailing newline and any other whitespace. */
310 for (convert
= convert_head
; convert
; convert
= convert
->next
) {
311 if (strcasecmp (convert
->type
, type
) == 0) { break; }
314 if (convert
->argstring
&&
315 strcasecmp (convert
->argstring
, argstring
)) {
316 adios (NULL
, "Multiple %s headers with different "
317 "argstrings not allowed", type
);
319 convert
->argstring
= mh_xstrdup(argstring
);
323 convert
->type
= mh_xstrdup(type
);
324 convert
->argstring
= mh_xstrdup(argstring
);
327 convert_tail
->next
= convert
;
329 convert_head
= convert
;
331 convert_tail
= convert
;
337 add_header (ct
, np
, vp
);
341 /* if this wasn't the last header field, then continue */
345 fseek (in
, (long) (-strlen (buf
)), SEEK_CUR
);
352 adios (NULL
, "message format error in component #%d", compnum
);
355 adios (NULL
, "getfld() returned %d", state
);
359 m_getfld_state_destroy (&gstate
);
361 if (header_encoding
!= CE_8BIT
) {
363 * Iterate through the list of headers and call the function to MIME-ify
367 for (hp
= ct
->c_first_hf
; hp
!= NULL
; hp
= hp
->next
) {
368 if (encode_rfc2047(hp
->name
, &hp
->value
, header_encoding
, NULL
)) {
369 adios(NULL
, "Unable to encode header \"%s\"", hp
->name
);
375 * Now add the MIME-Version header field
376 * to the list of header fields.
380 np
= mh_xstrdup(VRSN_FIELD
);
381 vp
= concat (" ", VRSN_VALUE
, "\n", NULL
);
382 add_header (ct
, np
, vp
);
386 * We initally assume we will find multiple contents in the
387 * draft. So create a multipart/mixed content to hold everything.
388 * We can remove this later, if it is not needed.
390 if (get_ctinfo ("multipart/mixed", ct
, 0) == NOTOK
)
392 ct
->c_type
= CT_MULTIPART
;
393 ct
->c_subtype
= MULTI_MIXED
;
396 ct
->c_ctparams
= (void *) m
;
400 * read and parse the composition file
401 * and the directives it contains.
403 while (fgetstr (buf
, sizeof(buf
) - 1, in
)) {
407 if (user_content (in
, buf
, &p
, infile
) == DONE
) {
408 admonish (NULL
, "ignoring spurious #end");
421 * Add any Attach headers to the list of MIME parts at the end of the
425 for (at_entry
= attach_head
; at_entry
; ) {
426 struct attach_list
*at_prev
= at_entry
;
430 if (access(at_entry
->filename
, R_OK
) != 0) {
431 adios("reading", "Unable to open %s for", at_entry
->filename
);
435 init_decoded_content(p
, infile
);
438 * Initialize our content structure based on the filename,
439 * and fill in all of the relevant fields. Also place MIME
440 * parameters in the attributes array.
443 setup_attach_content(p
, at_entry
->filename
);
450 at_entry
= at_entry
->next
;
451 free(at_prev
->filename
);
456 * Handle the mhbuild pseudoheaders, which deal with specific
465 /* In case there are multiple calls that land here, prevent leak. */
466 for (ctp
= cts
; ctp
&& *ctp
; ++ctp
) { free_content (*ctp
); }
469 /* Extract the type part (as a CT) from filename. */
470 cts
= mh_xcalloc(2, sizeof *cts
);
471 if (! (cts
[0] = parse_mime (convert_head
->filename
))) {
472 adios (NULL
, "failed to parse %s", convert_head
->filename
);
475 expand_pseudoheaders (cts
[0], m
, infile
, convert_head
);
477 /* Free the convert list. */
478 for (convert
= convert_head
; convert
; convert
= next
) {
479 next
= convert
->next
;
480 free (convert
->type
);
481 free (convert
->filename
);
482 free (convert
->argstring
);
489 * To allow for empty message bodies, if we've found NO content at all
490 * yet cook up an empty text/plain part.
499 init_decoded_content(p
, infile
);
501 if (get_ctinfo ("text/plain", p
, 0) == NOTOK
)
505 p
->c_subtype
= TEXT_PLAIN
;
506 p
->c_encoding
= CE_7BIT
;
508 * Sigh. ce_file contains the "decoded" contents of this part.
509 * So this seems like the best option available since we're going
510 * to call scan_content() on this.
512 p
->c_cefile
.ce_file
= mh_xstrdup("/dev/null");
513 p
->c_begin
= ftell(in
);
514 p
->c_end
= ftell(in
);
517 t
->tx_charset
= CHARSET_SPECIFIED
;
526 * close the composition draft since
527 * it's not needed any longer.
532 * If only one content was found, then remove and
533 * free the outer multipart content.
535 if (!m
->mp_parts
->mp_next
) {
538 p
= m
->mp_parts
->mp_part
;
539 m
->mp_parts
->mp_part
= NULL
;
541 /* move header fields */
542 p
->c_first_hf
= ct
->c_first_hf
;
543 p
->c_last_hf
= ct
->c_last_hf
;
544 ct
->c_first_hf
= NULL
;
545 ct
->c_last_hf
= NULL
;
554 * Fill out, or expand directives. Parse and execute
555 * commands specified by profile composition strings.
557 compose_content (ct
, verbose
);
559 if ((cp
= strchr(prefix
, 'a')) == NULL
)
560 adios (NULL
, "internal error(4)");
563 * If using EAI, force 8-bit charset.
565 if (header_encoding
== CE_8BIT
) {
570 * Scan the contents. Choose a transfer encoding, and
571 * check if prefix for multipart boundary clashes with
572 * any of the contents.
574 while (scan_content (ct
, maxunencoded
) == NOTOK
) {
579 adios (NULL
, "giving up trying to find a unique delimiter string");
585 /* Build the rest of the header field structures */
587 build_headers (ct
, header_encoding
);
594 * Set up structures for placing unencoded
595 * content when building parts.
599 init_decoded_content (CT ct
, const char *filename
)
601 ct
->c_ceopenfnx
= open7Bit
; /* since unencoded */
602 ct
->c_ceclosefnx
= close_encoding
;
603 ct
->c_cesizefnx
= NULL
; /* since unencoded */
604 ct
->c_encoding
= CE_7BIT
; /* Seems like a reasonable default */
605 ct
->c_file
= add(filename
, NULL
);
612 fgetstr (char *s
, int n
, FILE *stream
)
618 for (cp
= s
; cp
< ep
;) {
621 if (!fgets (cp
, n
, stream
))
622 return cp
== s
? NULL
: s
; /* "\\\nEOF" ignored. */
624 if (! do_direct() || (cp
== s
&& *cp
!= '#'))
625 return s
; /* Plaintext line. */
629 break; /* Can't contain "\\\n". */
630 cp
+= len
- 1; /* Just before NUL. */
631 if (*cp
-- != '\n' || *cp
!= '\\')
633 *cp
= '\0'; /* Erase the trailing "\\\n". */
637 if (strcmp(s
, "#on\n") == 0) {
639 } else if (strcmp(s
, "#off\n") == 0) {
641 } else if (strcmp(s
, "#pop\n") == 0) {
651 * Parse the composition draft for text and directives.
652 * Do initial setup of Content structure.
656 user_content (FILE *in
, char *buf
, CT
*ctp
, const char *infilename
)
664 struct str2init
*s2i
;
669 if (buf
[0] == '\n' || (do_direct() && strcmp (buf
, "#\n") == 0)) {
674 /* allocate basic Content structure */
678 /* allocate basic structure for handling decoded content */
679 init_decoded_content (ct
, infilename
);
686 * Handle inline text. Check if line
687 * is one of the following forms:
689 * 1) doesn't begin with '#' (implicit directive)
690 * 2) begins with "##" (implicit directive)
691 * 3) begins with "#<"
693 if (!do_direct() || buf
[0] != '#' || buf
[1] == '#' || buf
[1] == '<') {
697 char content
[BUFSIZ
];
701 if ((cp
= m_mktemp2(NULL
, invo_name
, NULL
, &out
)) == NULL
) {
702 adios("mhbuildsbr", "unable to create temporary file in %s",
706 /* use a temp file to collect the plain text lines */
707 ce
->ce_file
= mh_xstrdup(cp
);
710 if (do_direct() && (buf
[0] == '#' && buf
[1] == '<')) {
711 strncpy (content
, buf
+ 2, sizeof(content
));
717 /* the directive is implicit */
718 strncpy (content
, "text/plain", sizeof(content
));
720 strncpy (buffer
, (!do_direct() || buf
[0] != '#') ? buf
: buf
+ 1, sizeof(buffer
));
724 if (headers
>= 0 && do_direct() && uprf (buffer
, DESCR_FIELD
)
725 && buffer
[i
= strlen (DESCR_FIELD
)] == ':') {
729 ct
->c_descr
= add (buffer
+ i
+ 1, ct
->c_descr
);
730 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
731 adios (NULL
, "end-of-file after %s: field in plaintext", DESCR_FIELD
);
739 adios (NULL
, "#-directive after %s: field in plaintext", DESCR_FIELD
);
747 if (headers
>= 0 && do_direct() && uprf (buffer
, DISPO_FIELD
)
748 && buffer
[i
= strlen (DISPO_FIELD
)] == ':') {
752 ct
->c_dispo
= add (buffer
+ i
+ 1, ct
->c_dispo
);
753 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
754 adios (NULL
, "end-of-file after %s: field in plaintext", DISPO_FIELD
);
762 adios (NULL
, "#-directive after %s: field in plaintext", DISPO_FIELD
);
770 if (headers
!= 1 || buffer
[0] != '\n')
776 if ((cp
= fgetstr (buffer
, sizeof(buffer
) - 1, in
)) == NULL
)
778 if (do_direct() && buffer
[0] == '#') {
781 if (buffer
[1] != '#')
783 for (cp
= (bp
= buffer
) + 1; *cp
; cp
++)
790 ct
->c_end
= ftell (out
);
793 /* parse content type */
794 if (get_ctinfo (content
, ct
, inlineD
) == NOTOK
)
797 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
798 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
800 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
804 * check type specified (possibly implicitly)
806 switch (ct
->c_type
= s2i
->si_val
) {
808 if (!strcasecmp (ci
->ci_subtype
, "rfc822")) {
809 ct
->c_encoding
= CE_7BIT
;
814 adios (NULL
, "it doesn't make sense to define an in-line %s content",
815 ct
->c_type
== CT_MESSAGE
? "message" : "multipart");
820 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
821 (*ct
->c_ctinitfnx
) (ct
);
826 fseek (in
, pos
, SEEK_SET
);
831 * If we've reached this point, the next line
832 * must be some type of explicit directive.
835 /* check if directive is external-type */
836 extrnal
= (buf
[1] == '@');
838 /* parse directive */
839 if (get_ctinfo (buf
+ (extrnal
? 2 : 1), ct
, 1) == NOTOK
)
842 /* check directive against the list of MIME types */
843 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
844 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
848 * Check if the directive specified a valid type.
849 * This will happen if it was one of the following forms:
856 adios (NULL
, "missing subtype in \"#%s\"", ci
->ci_type
);
858 switch (ct
->c_type
= s2i
->si_val
) {
860 adios (NULL
, "use \"#begin ... #end\" instead of \"#%s/%s\"",
861 ci
->ci_type
, ci
->ci_subtype
);
865 if (!strcasecmp (ci
->ci_subtype
, "partial"))
866 adios (NULL
, "sorry, \"#%s/%s\" isn't supported",
867 ci
->ci_type
, ci
->ci_subtype
);
868 if (!strcasecmp (ci
->ci_subtype
, "external-body"))
869 adios (NULL
, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
870 ci
->ci_type
, ci
->ci_subtype
);
873 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
874 ci
->ci_type
, ci
->ci_subtype
);
878 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
879 (*ct
->c_ctinitfnx
) (ct
);
884 * #@type/subtype (external types directive)
891 adios (NULL
, "need external information for \"#@%s/%s\"",
892 ci
->ci_type
, ci
->ci_subtype
);
895 snprintf (buffer
, sizeof(buffer
), "message/external-body; %s", ci
->ci_magic
);
900 * Since we are using the current Content structure to
901 * hold information about the type of the external
902 * reference, we need to create another Content structure
903 * for the message/external-body to wrap it in.
906 init_decoded_content(ct
, infilename
);
908 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
910 ct
->c_type
= CT_MESSAGE
;
911 ct
->c_subtype
= MESSAGE_EXTERNAL
;
914 ct
->c_ctparams
= (void *) e
;
920 if (params_external (ct
, 1) == NOTOK
)
926 /* Handle [file] argument */
928 /* check if specifies command to execute */
929 if (*ci
->ci_magic
== '|' || *ci
->ci_magic
== '!') {
930 for (cp
= ci
->ci_magic
+ 1; isspace ((unsigned char) *cp
); cp
++)
933 adios (NULL
, "empty pipe command for #%s directive", ci
->ci_type
);
938 /* record filename of decoded contents */
939 ce
->ce_file
= ci
->ci_magic
;
940 if (access (ce
->ce_file
, R_OK
) == NOTOK
)
941 adios ("reading", "unable to access %s for", ce
->ce_file
);
942 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
943 ct
->c_end
= (long) st
.st_size
;
950 * No [file] argument, so check profile for
951 * method to compose content.
953 cp
= context_find_by_type ("compose", ci
->ci_type
, ci
->ci_subtype
);
955 content_error (NULL
, ct
, "don't know how to compose content");
958 ci
->ci_magic
= mh_xstrdup(cp
);
963 adios (NULL
, "external definition not allowed for \"#%s\"", ci
->ci_type
);
967 * #forw [+folder] [msgs]
969 if (!strcasecmp (ci
->ci_type
, "forw")) {
971 char *folder
, *arguments
[MAXARGS
];
975 ap
= brkstring (ci
->ci_magic
, " ", "\n");
976 copyip (ap
, arguments
, MAXARGS
);
978 arguments
[0] = "cur";
983 /* search the arguments for a folder name */
984 for (ap
= arguments
; *ap
; ap
++) {
986 if (*cp
== '+' || *cp
== '@') {
988 adios (NULL
, "only one folder per #forw directive");
990 folder
= pluspath (cp
);
994 /* else, use the current folder */
996 folder
= add (getfolder (1), NULL
);
998 if (!(mp
= folder_read (folder
, 0)))
999 adios (NULL
, "unable to read folder %s", folder
);
1000 for (ap
= arguments
; *ap
; ap
++) {
1002 if (*cp
!= '+' && *cp
!= '@')
1003 if (!m_convert (mp
, cp
))
1010 * If there is more than one message to include, make this
1011 * a content of type "multipart/digest" and insert each message
1012 * as a subpart. If there is only one message, then make this
1013 * a content of type "message/rfc822".
1015 if (mp
->numsel
> 1) {
1016 /* we are forwarding multiple messages */
1017 if (get_ctinfo ("multipart/digest", ct
, 0) == NOTOK
)
1019 ct
->c_type
= CT_MULTIPART
;
1020 ct
->c_subtype
= MULTI_DIGEST
;
1023 ct
->c_ctparams
= (void *) m
;
1026 for (msgnum
= mp
->lowsel
; msgnum
<= mp
->hghsel
; msgnum
++) {
1027 if (is_selected(mp
, msgnum
)) {
1033 init_decoded_content (p
, infilename
);
1035 if (get_ctinfo ("message/rfc822", p
, 0) == NOTOK
)
1037 p
->c_type
= CT_MESSAGE
;
1038 p
->c_subtype
= MESSAGE_RFC822
;
1040 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1041 pe
->ce_file
= mh_xstrdup(buffer
);
1042 if (listsw
&& stat (pe
->ce_file
, &st
) != NOTOK
)
1043 p
->c_end
= (long) st
.st_size
;
1047 pp
= &part
->mp_next
;
1052 /* we are forwarding one message */
1053 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
1055 ct
->c_type
= CT_MESSAGE
;
1056 ct
->c_subtype
= MESSAGE_RFC822
;
1058 msgnum
= mp
->lowsel
;
1059 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1060 ce
->ce_file
= mh_xstrdup(buffer
);
1061 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
1062 ct
->c_end
= (long) st
.st_size
;
1065 folder_free (mp
); /* free folder/message structure */
1072 if (!strcasecmp (ci
->ci_type
, "end")) {
1079 * #begin [ alternative | parallel ]
1081 if (!strcasecmp (ci
->ci_type
, "begin")) {
1082 if (!ci
->ci_magic
) {
1084 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1085 } else if (!strcasecmp (ci
->ci_magic
, "alternative")) {
1086 vrsn
= MULTI_ALTERNATE
;
1087 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1088 } else if (!strcasecmp (ci
->ci_magic
, "parallel")) {
1089 vrsn
= MULTI_PARALLEL
;
1090 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1091 } else if (uprf (ci
->ci_magic
, "digest")) {
1094 vrsn
= MULTI_UNKNOWN
;
1099 snprintf (buffer
, sizeof(buffer
), "multipart/%s", cp
);
1100 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
1102 ct
->c_type
= CT_MULTIPART
;
1103 ct
->c_subtype
= vrsn
;
1106 ct
->c_ctparams
= (void *) m
;
1109 while (fgetstr (buffer
, sizeof(buffer
) - 1, in
)) {
1113 if (user_content (in
, buffer
, &p
, infilename
) == DONE
) {
1115 adios (NULL
, "empty \"#begin ... #end\" sequence");
1123 pp
= &part
->mp_next
;
1126 admonish (NULL
, "premature end-of-file, missing #end");
1133 adios (NULL
, "unknown directive \"#%s\"", ci
->ci_type
);
1134 return NOTOK
; /* NOT REACHED */
1139 set_id (CT ct
, int top
)
1141 char contentid
[BUFSIZ
];
1143 static time_t clock
= 0;
1144 static char *msgfmt
;
1148 snprintf (contentid
, sizeof(contentid
), "%s\n", message_id (clock
, 1));
1150 msgfmt
= mh_xstrdup(contentid
);
1152 snprintf (contentid
, sizeof(contentid
), msgfmt
, top
? 0 : ++partno
);
1153 ct
->c_id
= mh_xstrdup(contentid
);
1158 * Fill out, or expand the various contents in the composition
1159 * draft. Read-in any necessary files. Parse and execute any
1160 * commands specified by profile composition strings.
1164 compose_content (CT ct
, int verbose
)
1166 CE ce
= &ct
->c_cefile
;
1168 switch (ct
->c_type
) {
1173 char partnam
[BUFSIZ
];
1174 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1178 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1179 pp
= partnam
+ strlen (partnam
);
1184 /* first, we call compose_content on all the subparts */
1185 for (part
= m
->mp_parts
, partnum
= 1; part
; part
= part
->mp_next
, partnum
++) {
1186 CT p
= part
->mp_part
;
1188 sprintf (pp
, "%d", partnum
);
1189 p
->c_partno
= mh_xstrdup(partnam
);
1190 if (compose_content (p
, verbose
) == NOTOK
)
1195 * If the -rfc934mode switch is given, then check all
1196 * the subparts of a multipart/digest. If they are all
1197 * message/rfc822, then mark this content and all
1198 * subparts with the rfc934 compatibility mode flag.
1200 if (rfc934sw
&& ct
->c_subtype
== MULTI_DIGEST
) {
1203 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1204 CT p
= part
->mp_part
;
1206 if (p
->c_subtype
!= MESSAGE_RFC822
) {
1211 ct
->c_rfc934
= is934
;
1212 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1213 CT p
= part
->mp_part
;
1215 if ((p
->c_rfc934
= is934
))
1221 ct
->c_end
= (partnum
= strlen (prefix
) + 2) + 2;
1225 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
)
1226 ct
->c_end
+= part
->mp_part
->c_end
+ partnum
;
1232 /* Nothing to do for type message */
1236 * Discrete types (text/application/audio/image/video)
1241 int i
, xstdout
, len
, buflen
;
1243 char *vec
[4], buffer
[BUFSIZ
];
1245 CI ci
= &ct
->c_ctinfo
;
1248 if (!(cp
= ci
->ci_magic
))
1249 adios (NULL
, "internal error(5)");
1251 if ((tfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1252 adios("mhbuildsbr", "unable to create temporary file in %s",
1255 ce
->ce_file
= mh_xstrdup(tfile
);
1260 /* Get buffer ready to go */
1263 buflen
= sizeof(buffer
);
1266 * Parse composition string into buffer
1268 for ( ; *cp
; cp
++) {
1273 /* insert parameters from directive */
1277 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1278 snprintf (bp
, buflen
, "%s%s=\"%s\"", s
,
1279 pm
->pm_name
, get_param_value(pm
, '?'));
1289 /* %f, and stdout is not-redirected */
1295 * insert temporary filename where
1296 * content should be written
1298 snprintf (bp
, buflen
, "%s", ce
->ce_file
);
1302 /* insert content subtype */
1303 strncpy (bp
, ci
->ci_subtype
, buflen
);
1307 /* insert character % */
1328 printf ("composing content %s/%s from command\n\t%s\n",
1329 ci
->ci_type
, ci
->ci_subtype
, buffer
);
1331 fflush (stdout
); /* not sure if need for -noverbose */
1338 if ((out
= fopen (ce
->ce_file
, "w")) == NULL
)
1339 adios (ce
->ce_file
, "unable to open for writing");
1341 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
> 5; i
++)
1345 adios ("fork", "unable to fork");
1350 dup2 (fileno (out
), 1);
1351 close (fileno (out
));
1352 execvp ("/bin/sh", vec
);
1353 fprintf (stderr
, "unable to exec ");
1360 if (pidXwait(child_id
, NULL
))
1366 /* Check size of file */
1367 if (listsw
&& ct
->c_end
== 0L) {
1370 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1371 ct
->c_end
= (long) st
.st_size
;
1383 * 1) choose a transfer encoding.
1384 * 2) check for clashes with multipart boundary string.
1385 * 3) for text content, figure out which character set is being used.
1387 * If there is a clash with one of the contents and the multipart boundary,
1388 * this function will exit with NOTOK. This will cause the scanning process
1389 * to be repeated with a different multipart boundary. It is possible
1390 * (although highly unlikely) that this scan will be repeated multiple times.
1394 scan_content (CT ct
, size_t maxunencoded
)
1397 int check8bit
= 0, contains8bit
= 0; /* check if contains 8bit data */
1398 int checknul
= 0, containsnul
= 0; /* check if contains NULs */
1399 int checklinelen
= 0, linelen
= 0; /* check for long lines */
1400 int checkllinelen
= 0; /* check for extra-long lines */
1401 int checkboundary
= 0, boundaryclash
= 0; /* check if clashes with multipart boundary */
1402 int checklinespace
= 0, linespace
= 0; /* check if any line ends with space */
1407 struct text
*t
= NULL
;
1409 CE ce
= &ct
->c_cefile
;
1412 * handle multipart by scanning all subparts
1413 * and then checking their encoding.
1415 if (ct
->c_type
== CT_MULTIPART
) {
1416 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1419 /* initially mark the domain of enclosing multipart as 7bit */
1420 ct
->c_encoding
= CE_7BIT
;
1422 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1423 CT p
= part
->mp_part
;
1425 if (scan_content (p
, maxunencoded
) == NOTOK
) /* choose encoding for subpart */
1428 /* if necessary, enlarge encoding for enclosing multipart */
1429 if (p
->c_encoding
== CE_BINARY
)
1430 ct
->c_encoding
= CE_BINARY
;
1431 if (p
->c_encoding
== CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
)
1432 ct
->c_encoding
= CE_8BIT
;
1439 * Decide what to check while scanning this content. Note that
1440 * for text content we always check for 8bit characters if the
1441 * charset is unspecified, because that controls whether or not the
1442 * character set is us-ascii or retrieved from the locale. And
1443 * we check even if the charset is specified, to allow setting
1444 * the proper Content-Transfer-Encoding.
1447 if (ct
->c_type
== CT_TEXT
) {
1448 t
= (struct text
*) ct
->c_ctparams
;
1449 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
1455 switch (ct
->c_reqencoding
) {
1466 /* Use the default rules based on content-type */
1467 switch (ct
->c_type
) {
1471 if (ct
->c_subtype
== TEXT_PLAIN
) {
1478 case CT_APPLICATION
:
1490 /* don't check anything for message/external */
1491 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1504 * Don't check anything for these types,
1505 * since we are forcing use of base64, unless
1506 * the content-type was specified by a mhbuild directive.
1517 * Scan the unencoded content
1519 if (check8bit
|| checklinelen
|| checklinespace
|| checkboundary
||
1520 checkllinelen
|| checknul
) {
1521 if ((in
= fopen (ce
->ce_file
, "r")) == NULL
)
1522 adios (ce
->ce_file
, "unable to open for reading");
1523 prefix_len
= strlen (prefix
);
1525 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1527 * Check for 8bit and NUL data.
1529 for (cp
= bufp
; (check8bit
|| checknul
) &&
1530 cp
< bufp
+ gotlen
; cp
++) {
1531 if (!isascii ((unsigned char) *cp
)) {
1533 check8bit
= 0; /* no need to keep checking */
1537 checknul
= 0; /* no need to keep checking */
1542 * Check line length.
1544 if (checklinelen
&& ((size_t)gotlen
> maxunencoded
+ 1)) {
1546 checklinelen
= 0; /* no need to keep checking */
1550 * RFC 5322 specifies that a message cannot contain a line
1551 * greater than 998 characters (excluding the CRLF). If we
1552 * get one of those lines and linelen is NOT set, then abort.
1555 if (checkllinelen
&& !linelen
&&
1556 (gotlen
> MAXLONGLINE
+ 1)) {
1557 adios(NULL
, "Line in content exceeds maximum line limit (%d)",
1562 * Check if line ends with a space.
1564 if (checklinespace
&& (cp
= bufp
+ gotlen
- 2) > bufp
&&
1565 isspace ((unsigned char) *cp
)) {
1567 checklinespace
= 0; /* no need to keep checking */
1571 * Check if content contains a line that clashes
1572 * with our standard boundary for multipart messages.
1574 if (checkboundary
&& bufp
[0] == '-' && bufp
[1] == '-') {
1575 for (cp
= bufp
+ gotlen
- 1; cp
>= bufp
; cp
--)
1576 if (!isspace ((unsigned char) *cp
))
1579 if (!strncmp(bufp
+ 2, prefix
, prefix_len
) &&
1580 isdigit((unsigned char) bufp
[2 + prefix_len
])) {
1582 checkboundary
= 0; /* no need to keep checking */
1591 * If the content is text and didn't specify a character set,
1592 * we need to figure out which one was used.
1594 set_charset (ct
, contains8bit
);
1597 * Decide which transfer encoding to use.
1600 if (ct
->c_reqencoding
!= CE_UNKNOWN
)
1601 ct
->c_encoding
= ct
->c_reqencoding
;
1603 int wants_q_p
= (containsnul
|| linelen
|| linespace
|| checksw
);
1605 switch (ct
->c_type
) {
1608 ct
->c_encoding
= CE_QUOTED
;
1609 else if (contains8bit
)
1610 ct
->c_encoding
= CE_8BIT
;
1612 ct
->c_encoding
= CE_7BIT
;
1616 case CT_APPLICATION
:
1617 /* For application type, use base64, except when postscript */
1618 if (wants_q_p
|| contains8bit
) {
1619 if (ct
->c_subtype
== APPLICATION_POSTSCRIPT
)
1620 ct
->c_encoding
= CE_QUOTED
; /* historical */
1622 ct
->c_encoding
= CE_BASE64
;
1624 ct
->c_encoding
= CE_7BIT
;
1629 ct
->c_encoding
= contains8bit
? CE_8BIT
: CE_7BIT
;
1635 /* For audio, image, and video contents, just use base64 */
1636 ct
->c_encoding
= CE_BASE64
;
1641 return (boundaryclash
? NOTOK
: OK
);
1646 * Scan the content structures, and build header
1647 * fields that will need to be output into the
1652 build_headers (CT ct
, int header_encoding
)
1654 int cc
, mailbody
, extbody
, len
;
1655 char *np
, *vp
, buffer
[BUFSIZ
];
1656 CI ci
= &ct
->c_ctinfo
;
1659 * If message is type multipart, then add the multipart
1660 * boundary to the list of attribute/value pairs.
1662 if (ct
->c_type
== CT_MULTIPART
) {
1663 static int level
= 0; /* store nesting level */
1665 snprintf (buffer
, sizeof(buffer
), "%s%d", prefix
, level
++);
1666 add_param(&ci
->ci_first_pm
, &ci
->ci_last_pm
, "boundary", buffer
, 0);
1670 * Skip the output of Content-Type, parameters, content
1671 * description and disposition, and Content-ID if the
1672 * content is of type "message" and the rfc934 compatibility
1673 * flag is set (which means we are inside multipart/digest
1674 * and the switch -rfc934mode was given).
1676 if (ct
->c_type
== CT_MESSAGE
&& ct
->c_rfc934
)
1680 * output the content type and subtype
1682 np
= mh_xstrdup(TYPE_FIELD
);
1683 vp
= concat (" ", ci
->ci_type
, "/", ci
->ci_subtype
, NULL
);
1685 /* keep track of length of line */
1686 len
= strlen (TYPE_FIELD
) + strlen (ci
->ci_type
)
1687 + strlen (ci
->ci_subtype
) + 3;
1689 extbody
= ct
->c_type
== CT_MESSAGE
&& ct
->c_subtype
== MESSAGE_EXTERNAL
;
1690 mailbody
= extbody
&& ((struct exbody
*) ct
->c_ctparams
)->eb_body
;
1693 * Append the attribute/value pairs to
1694 * the end of the Content-Type line.
1697 if (ci
->ci_first_pm
) {
1698 char *s
= output_params(len
, ci
->ci_first_pm
, &len
, mailbody
);
1701 adios(NULL
, "Internal error: failed outputting Content-Type "
1709 * Append any RFC-822 comment to the end of
1710 * the Content-Type line.
1712 if (ci
->ci_comment
) {
1713 snprintf (buffer
, sizeof(buffer
), "(%s)", ci
->ci_comment
);
1714 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
1715 vp
= add ("\n\t", vp
);
1721 vp
= add (buffer
, vp
);
1724 vp
= add ("\n", vp
);
1725 add_header (ct
, np
, vp
);
1728 * output the Content-ID, unless disabled by -nocontentid
1730 if (contentidsw
&& ct
->c_id
) {
1731 np
= mh_xstrdup(ID_FIELD
);
1732 vp
= concat (" ", ct
->c_id
, NULL
);
1733 add_header (ct
, np
, vp
);
1736 * output the Content-Description
1739 np
= mh_xstrdup(DESCR_FIELD
);
1740 vp
= concat (" ", ct
->c_descr
, NULL
);
1741 if (header_encoding
!= CE_8BIT
) {
1742 if (encode_rfc2047(DESCR_FIELD
, &vp
, header_encoding
, NULL
)) {
1743 adios(NULL
, "Unable to encode %s header", DESCR_FIELD
);
1746 add_header (ct
, np
, vp
);
1750 * output the Content-Disposition. If it's NULL but c_dispo_type is
1751 * set, then we need to build it.
1754 np
= mh_xstrdup(DISPO_FIELD
);
1755 vp
= concat (" ", ct
->c_dispo
, NULL
);
1756 add_header (ct
, np
, vp
);
1757 } else if (ct
->c_dispo_type
) {
1758 vp
= concat (" ", ct
->c_dispo_type
, NULL
);
1759 len
= strlen(DISPO_FIELD
) + strlen(vp
) + 1;
1760 np
= output_params(len
, ct
->c_dispo_first
, NULL
, 0);
1764 add_header (ct
, mh_xstrdup(DISPO_FIELD
), vp
);
1769 * If this is the internal content structure for a
1770 * "message/external", then we are done with the
1771 * headers (since it has no body).
1777 * output the Content-MD5
1780 np
= mh_xstrdup(MD5_FIELD
);
1781 vp
= calculate_digest (ct
, (ct
->c_encoding
== CE_QUOTED
) ? 1 : 0);
1782 add_header (ct
, np
, vp
);
1786 * output the Content-Transfer-Encoding
1787 * If using EAI and message body is 7-bit, force 8-bit C-T-E.
1789 if (header_encoding
== CE_8BIT
&& ct
->c_encoding
== CE_7BIT
) {
1790 ct
->c_encoding
= CE_8BIT
;
1793 switch (ct
->c_encoding
) {
1795 /* Nothing to output */
1799 np
= mh_xstrdup(ENCODING_FIELD
);
1800 vp
= concat (" ", "8bit", "\n", NULL
);
1801 add_header (ct
, np
, vp
);
1805 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1806 adios (NULL
, "internal error, invalid encoding");
1808 np
= mh_xstrdup(ENCODING_FIELD
);
1809 vp
= concat (" ", "quoted-printable", "\n", NULL
);
1810 add_header (ct
, np
, vp
);
1814 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1815 adios (NULL
, "internal error, invalid encoding");
1817 np
= mh_xstrdup(ENCODING_FIELD
);
1818 vp
= concat (" ", "base64", "\n", NULL
);
1819 add_header (ct
, np
, vp
);
1823 if (ct
->c_type
== CT_MESSAGE
)
1824 adios (NULL
, "internal error, invalid encoding");
1826 np
= mh_xstrdup(ENCODING_FIELD
);
1827 vp
= concat (" ", "binary", "\n", NULL
);
1828 add_header (ct
, np
, vp
);
1832 adios (NULL
, "unknown transfer encoding in content");
1837 * Additional content specific header processing
1839 switch (ct
->c_type
) {
1842 struct multipart
*m
;
1845 m
= (struct multipart
*) ct
->c_ctparams
;
1846 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1850 build_headers (p
, header_encoding
);
1856 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1859 e
= (struct exbody
*) ct
->c_ctparams
;
1860 build_headers (e
->eb_content
, header_encoding
);
1873 static char nib2b64
[0x40+1] =
1874 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1877 calculate_digest (CT ct
, int asciiP
)
1882 unsigned char digest
[16];
1883 unsigned char outbuf
[25];
1885 CE ce
= &ct
->c_cefile
;
1886 char *infilename
= ce
->ce_file
? ce
->ce_file
: ct
->c_file
;
1890 if ((in
= fopen (infilename
, "r")) == NULL
)
1891 adios (infilename
, "unable to open for reading");
1893 /* Initialize md5 context */
1894 MD5Init (&mdContext
);
1896 /* calculate md5 message digest */
1901 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1904 cp
= bufp
+ gotlen
- 1;
1905 if ((c
= *cp
) == '\n')
1908 MD5Update (&mdContext
, (unsigned char *) bufp
,
1909 (unsigned int) gotlen
);
1912 MD5Update (&mdContext
, (unsigned char *) "\r\n", 2);
1915 char buffer
[BUFSIZ
];
1916 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), in
)) > 0)
1917 MD5Update (&mdContext
, (unsigned char *) buffer
, (unsigned int) cc
);
1920 /* md5 finalization. Write digest and zero md5 context */
1921 MD5Final (digest
, &mdContext
);
1926 /* print debugging info */
1930 fprintf (stderr
, "MD5 digest=");
1931 for (ep
= (dp
= digest
) + sizeof(digest
) / sizeof(digest
[0]);
1933 fprintf (stderr
, "%02x", *dp
& 0xff);
1934 fprintf (stderr
, "\n");
1937 /* encode the digest using base64 */
1938 for (dp
= digest
, op
= (char *) outbuf
,
1939 cc
= sizeof(digest
) / sizeof(digest
[0]);
1940 cc
> 0; cc
-= 3, op
+= 4) {
1944 bits
= (*dp
++ & 0xff) << 16;
1946 bits
|= (*dp
++ & 0xff) << 8;
1948 bits
|= *dp
++ & 0xff;
1951 for (bp
= op
+ 4; bp
> op
; bits
>>= 6)
1952 *--bp
= nib2b64
[bits
& 0x3f];
1960 /* null terminate string */
1963 /* now make copy and return string */
1964 vp
= concat (" ", outbuf
, "\n", NULL
);
1969 * Set things up for the content structure for file "filename" that
1974 setup_attach_content(CT ct
, char *filename
)
1976 char *type
, *simplename
= r1bindex(filename
, '/');
1977 struct str2init
*s2i
;
1980 if (! (type
= mime_type(filename
))) {
1981 adios(NULL
, "Unable to determine MIME type of \"%s\"", filename
);
1985 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1986 * since we're just feeding it a MIME type we have to add those ourself.
1987 * Map that to a valid content-type label and call any initialization
1991 if (get_ctinfo(type
, ct
, 0) == NOTOK
)
1996 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
1997 if (strcasecmp(ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0)
1999 if (!s2i
->si_key
&& !uprf(ct
->c_ctinfo
.ci_type
, "X-"))
2003 * Make sure the type isn't incompatible with what we can handle
2006 switch (ct
->c_type
= s2i
->si_val
) {
2008 adios (NULL
, "multipart types must be specified by mhbuild directives");
2012 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "partial") == 0)
2013 adios(NULL
, "Sorry, %s/%s isn't supported", ct
->c_ctinfo
.ci_type
,
2014 ct
->c_ctinfo
.ci_subtype
);
2015 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "external-body") == 0)
2016 adios(NULL
, "external-body messages must be specified "
2017 "by mhbuild directives");
2022 * This sets the subtype, if it's significant
2024 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
2025 (*ct
->c_ctinitfnx
)(ct
);
2030 * Feed in a few attributes; specifically, the name attribute, the
2031 * content-description, and the content-disposition.
2034 for (pm
= ct
->c_ctinfo
.ci_first_pm
; pm
; pm
= pm
->pm_next
) {
2035 if (strcasecmp(pm
->pm_name
, "name") == 0) {
2036 mh_xfree(pm
->pm_value
);
2037 pm
->pm_value
= mh_xstrdup(simplename
);
2043 add_param(&ct
->c_ctinfo
.ci_first_pm
, &ct
->c_ctinfo
.ci_last_pm
,
2044 "name", simplename
, 0);
2046 ct
->c_descr
= mh_xstrdup(simplename
);
2047 ct
->c_descr
= add("\n", ct
->c_descr
);
2048 ct
->c_cefile
.ce_file
= mh_xstrdup(filename
);
2050 set_disposition (ct
);
2052 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename", simplename
, 0);
2056 * If disposition type hasn't already been set in ct:
2057 * Look for mhbuild-disposition-<type>/<subtype> entry
2058 * that specifies Content-Disposition type. Only
2059 * 'attachment' and 'inline' are allowed. Default to
2063 set_disposition (CT ct
) {
2064 if (ct
->c_dispo_type
== NULL
) {
2065 char *cp
= context_find_by_type ("disposition", ct
->c_ctinfo
.ci_type
,
2066 ct
->c_ctinfo
.ci_subtype
);
2068 if (cp
&& strcasecmp (cp
, "attachment") &&
2069 strcasecmp (cp
, "inline")) {
2070 admonish (NULL
, "configuration problem: %s-disposition-%s%s%s "
2071 "specifies '%s' but only 'attachment' and 'inline' are "
2072 "allowed", invo_name
,
2073 ct
->c_ctinfo
.ci_type
,
2074 ct
->c_ctinfo
.ci_subtype
? "/" : "",
2075 ct
->c_ctinfo
.ci_subtype
? ct
->c_ctinfo
.ci_subtype
: "",
2081 ct
->c_dispo_type
= mh_xstrdup(cp
);
2086 * Set text content charset if it was unspecified. contains8bit
2088 * 0: content does not contain 8-bit characters
2089 * 1: content contains 8-bit characters
2090 * -1: ignore content and use user's locale to determine charset
2093 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 adios (NULL
, "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
) {
2133 /* text_plain_ct is used to concatenate all of the text/plain
2134 replies into one part, instead of having each one in a separate
2136 CT text_plain_ct
= NULL
;
2138 switch (ct
->c_type
) {
2139 case CT_MULTIPART
: {
2140 struct multipart
*mp
= (struct multipart
*) ct
->c_ctparams
;
2143 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
2146 /* The parts are in descending priority order (defined by
2147 RFC 2046 Sec. 5.1.4) because they were reversed by
2148 parse_mime (). So, stop looking for matches with
2149 immediate subparts after the first match of an
2151 for (part
= mp
->mp_parts
; ! matched
&& part
; part
= part
->mp_next
) {
2152 char *type_subtype
=
2153 concat (part
->mp_part
->c_ctinfo
.ci_type
, "/",
2154 part
->mp_part
->c_ctinfo
.ci_subtype
, NULL
);
2156 if (part
->mp_part
->c_type
== CT_MULTIPART
) {
2157 expand_pseudoheaders (part
->mp_part
, m
, infile
,
2160 const convert_list
*c
;
2162 for (c
= convert_head
; c
; c
= c
->next
) {
2163 if (! strcasecmp (type_subtype
, c
->type
)) {
2164 expand_pseudoheader (part
->mp_part
, &text_plain_ct
,
2166 c
->type
, c
->argstring
);
2172 free (type_subtype
);
2175 for (part
= mp
->mp_parts
; part
; part
= part
->mp_next
) {
2176 expand_pseudoheaders (part
->mp_part
, m
, infile
, convert_head
);
2183 char *type_subtype
=
2184 concat (ct
->c_ctinfo
.ci_type
, "/", ct
->c_ctinfo
.ci_subtype
,
2186 const convert_list
*c
;
2188 for (c
= convert_head
; c
; c
= c
->next
) {
2189 if (! strcasecmp (type_subtype
, c
->type
)) {
2190 expand_pseudoheader (ct
, &text_plain_ct
, m
, infile
, c
->type
,
2195 free (type_subtype
);
2203 * Expand a single pseudoheader. It's for the specified type.
2206 expand_pseudoheader (CT ct
, CT
*text_plain_ct
, struct multipart
*m
,
2207 const char *infile
, const char *type
,
2208 const char *argstring
) {
2210 FILE *reply_fp
= NULL
;
2211 char *convert
, *type_p
, *subtype_p
;
2212 char *convert_command
;
2213 char *charset
= NULL
;
2215 struct str2init
*s2i
;
2221 type_p
= getcpy (type
);
2222 if ((subtype_p
= strchr (type_p
, '/'))) {
2223 *subtype_p
++ = '\0';
2224 convert
= context_find_by_type ("convert", type_p
, subtype_p
);
2227 type_p
= concat ("mhbuild-convert-", type
, NULL
);
2228 convert
= context_find (type_p
);
2233 /* No mhbuild-convert- entry in mhn.defaults or profile
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
, " ", argstring
? argstring
: "", " >", reply_file
,
2243 /* Convert here . . . */
2244 ct
->c_storeproc
= mh_xstrdup(convert_command
);
2245 ct
->c_umask
= ~m_gmprot ();
2247 if ((status
= show_content_aux (ct
, 0, convert_command
, NULL
, NULL
)) !=
2249 admonish (NULL
, "store of %s content failed", type
);
2251 free (convert_command
);
2253 /* Fill out the the new ct, reply_ct. */
2255 init_decoded_content (reply_ct
, infile
);
2257 if (extract_headers (reply_ct
, reply_file
, &reply_fp
) == NOTOK
) {
2260 "failed to extract headers from convert output in %s",
2265 /* For text content only, see if it is 8-bit text. */
2266 if (reply_ct
->c_type
== CT_TEXT
) {
2269 if ((fd
= open (reply_file
, O_RDONLY
)) == NOTOK
||
2270 scan_input (fd
, &eightbit
) == NOTOK
) {
2272 admonish (NULL
, "failed to read %s", reply_file
);
2278 /* This sets reply_ct->c_ctparams, and reply_ct->c_termproc if the
2279 charset can't be handled natively. */
2280 for (s2i
= str2cts
; s2i
->si_key
; s2i
++) {
2281 if (strcasecmp(reply_ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0) {
2286 if ((reply_ct
->c_ctinitfnx
= s2i
->si_init
)) {
2287 (*reply_ct
->c_ctinitfnx
)(reply_ct
);
2291 get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1))) {
2292 /* The reply Content-Type had the charset. */
2295 set_charset (reply_ct
, -1);
2296 charset
= get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1);
2297 if (reply_ct
->c_reqencoding
== CE_UNKNOWN
&&
2298 reply_ct
->c_type
== CT_TEXT
) {
2299 /* Assume that 8bit is sufficient (for text). In other words,
2300 don't allow it to be encoded as quoted printable if lines
2301 are too long. This also sidesteps the check for whether
2302 it needs to be encoded as binary; instead, it relies on
2303 the applicable mhbuild-convert-text directive to ensure
2304 that the resultant text is not binary. */
2305 reply_ct
->c_reqencoding
= eightbit
? CE_8BIT
: CE_7BIT
;
2309 /* Concatenate text/plain parts. */
2310 if (reply_ct
->c_type
== CT_TEXT
&&
2311 reply_ct
->c_subtype
== TEXT_PLAIN
) {
2312 if (! *text_plain_ct
&& m
->mp_parts
&& m
->mp_parts
->mp_part
&&
2313 m
->mp_parts
->mp_part
->c_type
== CT_TEXT
&&
2314 m
->mp_parts
->mp_part
->c_subtype
== TEXT_PLAIN
) {
2315 *text_plain_ct
= m
->mp_parts
->mp_part
;
2316 /* Make sure that the charset is set in the text/plain
2318 set_charset (*text_plain_ct
, -1);
2319 if ((*text_plain_ct
)->c_reqencoding
== CE_UNKNOWN
) {
2320 /* Assume that 8bit is sufficient (for text). In other words,
2321 don't allow it to be encoded as quoted printable if lines
2322 are too long. This also sidesteps the check for whether
2323 it needs to be encoded as binary; instead, it relies on
2324 the applicable mhbuild-convert-text directive to ensure
2325 that the resultant text is not binary. */
2326 (*text_plain_ct
)->c_reqencoding
=
2327 eightbit
? CE_8BIT
: CE_7BIT
;
2331 if (*text_plain_ct
) {
2332 /* Only concatenate if the charsets are identical. */
2333 char *text_plain_ct_charset
=
2334 get_param ((*text_plain_ct
)->c_ctinfo
.ci_first_pm
, "charset",
2337 if (strcasecmp (text_plain_ct_charset
, charset
) == 0) {
2338 /* Append this text/plain reply to the first one.
2339 If there's a problem anywhere along the way,
2340 instead attach it is a separate part. */
2341 int text_plain_reply
=
2342 open ((*text_plain_ct
)->c_cefile
.ce_file
,
2343 O_WRONLY
| O_APPEND
);
2344 int addl_reply
= open (reply_file
, O_RDONLY
);
2346 if (text_plain_reply
!= NOTOK
&& addl_reply
!= NOTOK
) {
2347 /* Insert blank line before each addl part. */
2348 /* It would be nice not to do this for the first one. */
2349 if (write (text_plain_reply
, "\n", 1) == 1) {
2350 /* Copy the text from the new reply and
2351 then free its Content struct. */
2352 cpydata (addl_reply
, text_plain_reply
,
2353 (*text_plain_ct
)->c_cefile
.ce_file
,
2355 if (close (text_plain_reply
) == OK
&&
2356 close (addl_reply
) == OK
) {
2357 /* If appended text needed 8-bit but first text didn't,
2358 propagate the 8-bit indication. */
2359 if ((*text_plain_ct
)->c_reqencoding
== CE_7BIT
&&
2360 reply_ct
->c_reqencoding
== CE_8BIT
) {
2361 (*text_plain_ct
)->c_reqencoding
= CE_8BIT
;
2364 if (reply_fp
) { fclose (reply_fp
); }
2366 free_content (reply_ct
);
2373 *text_plain_ct
= reply_ct
;
2377 reply_ct
->c_cefile
.ce_file
= reply_file
;
2378 reply_ct
->c_cefile
.ce_fp
= reply_fp
;
2379 reply_ct
->c_cefile
.ce_unlink
= 1;
2381 /* Attach the new part to the parent multipart/mixed, "m". */
2383 part
->mp_part
= reply_ct
;
2387 for (p
= m
->mp_parts
; p
&& p
->mp_next
; p
= p
->mp_next
) { continue; }
2395 /* Extract any Content-Type header from beginning of convert output. */
2397 extract_headers (CT ct
, char *reply_file
, FILE **reply_fp
) {
2398 char *buffer
= NULL
, *cp
, *end_of_header
;
2399 int found_header
= 0;
2400 struct stat statbuf
;
2402 /* Read the convert reply from the file to memory. */
2403 if (stat (reply_file
, &statbuf
) == NOTOK
) {
2404 admonish (reply_file
, "failed to stat");
2405 goto failed_to_extract_ct
;
2408 buffer
= mh_xmalloc (statbuf
.st_size
+ 1);
2410 if ((*reply_fp
= fopen (reply_file
, "r+")) == NULL
||
2411 fread (buffer
, 1, (size_t) statbuf
.st_size
, *reply_fp
) <
2412 (size_t) statbuf
.st_size
) {
2413 admonish (reply_file
, "failed to read");
2414 goto failed_to_extract_ct
;
2416 buffer
[statbuf
.st_size
] = '\0';
2418 /* Look for a header in the convert reply. */
2419 if (strncasecmp (buffer
, TYPE_FIELD
, strlen (TYPE_FIELD
)) == 0 &&
2420 buffer
[strlen (TYPE_FIELD
)] == ':') {
2421 if ((end_of_header
= strstr (buffer
, "\r\n\r\n"))) {
2424 } else if ((end_of_header
= strstr (buffer
, "\n\n"))) {
2436 /* Truncate buffer to just the C-T. */
2437 *end_of_header
= '\0';
2438 n
= strlen (buffer
);
2440 if (get_ctinfo (buffer
+ 14, ct
, 0) != OK
) {
2441 admonish (NULL
, "unable to get content info for reply");
2442 goto failed_to_extract_ct
;
2445 /* Hack. Use parse_mime() to detect the type/subtype of the
2446 reply, which we'll use below. */
2447 tmp_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2448 if ((tmp_f
= fopen (tmp_file
, "w")) &&
2449 fwrite (buffer
, 1, n
, tmp_f
) == n
) {
2452 goto failed_to_extract_ct
;
2454 tmp_ct
= parse_mime (tmp_file
);
2457 /* The type and subtype were detected from the reply
2458 using parse_mime() above. */
2459 ct
->c_type
= tmp_ct
->c_type
;
2460 ct
->c_subtype
= tmp_ct
->c_subtype
;
2461 free_content (tmp_ct
);
2466 /* Rewrite the content without the header. */
2467 cp
= end_of_header
+ 1;
2470 if (fwrite (cp
, 1, statbuf
.st_size
- (cp
- buffer
), *reply_fp
) <
2471 (size_t) (statbuf
.st_size
- (cp
- buffer
))) {
2472 admonish (reply_file
, "failed to write");
2473 goto failed_to_extract_ct
;
2476 if (ftruncate (fileno (*reply_fp
), statbuf
.st_size
- (cp
- buffer
)) !=
2478 advise (reply_file
, "ftruncate");
2479 goto failed_to_extract_ct
;
2482 /* No header section, assume the reply is text/plain. */
2483 ct
->c_type
= CT_TEXT
;
2484 ct
->c_subtype
= TEXT_PLAIN
;
2485 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
) {
2486 /* This never should fail, but just in case. */
2487 adios (NULL
, "unable to get content info for reply");
2491 /* free_encoding() will close reply_fp, which is passed through
2492 ct->c_cefile.ce_fp. */
2496 failed_to_extract_ct
:
2497 if (*reply_fp
) { fclose (*reply_fp
); }