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
= add (name
, NULL
);
208 vp
= add (buf
, NULL
);
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
= add (VRSN_FIELD
, NULL
);
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 (ep
= (cp
= s
) + o_n
; cp
< ep
; ) {
621 if (!fgets (cp
, n
, stream
))
622 return (cp
!= s
? s
: NULL
);
624 if (cp
== s
&& *cp
!= '#')
627 cp
+= (i
= strlen (cp
)) - 1;
628 if (i
<= 1 || *cp
-- != '\n' || *cp
!= '\\')
634 if (strcmp(s
, "#on\n") == 0) {
636 } else if (strcmp(s
, "#off\n") == 0) {
638 } else if (strcmp(s
, "#pop\n") == 0) {
650 * Parse the composition draft for text and directives.
651 * Do initial setup of Content structure.
655 user_content (FILE *in
, char *buf
, CT
*ctp
, const char *infilename
)
663 struct str2init
*s2i
;
668 if (buf
[0] == '\n' || (do_direct() && strcmp (buf
, "#\n") == 0)) {
673 /* allocate basic Content structure */
677 /* allocate basic structure for handling decoded content */
678 init_decoded_content (ct
, infilename
);
685 * Handle inline text. Check if line
686 * is one of the following forms:
688 * 1) doesn't begin with '#' (implicit directive)
689 * 2) begins with "##" (implicit directive)
690 * 3) begins with "#<"
692 if (!do_direct() || buf
[0] != '#' || buf
[1] == '#' || buf
[1] == '<') {
696 char content
[BUFSIZ
];
700 if ((cp
= m_mktemp2(NULL
, invo_name
, NULL
, &out
)) == NULL
) {
701 adios("mhbuildsbr", "unable to create temporary file in %s",
705 /* use a temp file to collect the plain text lines */
706 ce
->ce_file
= add (cp
, NULL
);
709 if (do_direct() && (buf
[0] == '#' && buf
[1] == '<')) {
710 strncpy (content
, buf
+ 2, sizeof(content
));
716 /* the directive is implicit */
717 strncpy (content
, "text/plain", sizeof(content
));
719 strncpy (buffer
, (!do_direct() || buf
[0] != '#') ? buf
: buf
+ 1, sizeof(buffer
));
723 if (headers
>= 0 && do_direct() && uprf (buffer
, DESCR_FIELD
)
724 && buffer
[i
= strlen (DESCR_FIELD
)] == ':') {
728 ct
->c_descr
= add (buffer
+ i
+ 1, ct
->c_descr
);
729 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
730 adios (NULL
, "end-of-file after %s: field in plaintext", DESCR_FIELD
);
738 adios (NULL
, "#-directive after %s: field in plaintext", DESCR_FIELD
);
746 if (headers
>= 0 && do_direct() && uprf (buffer
, DISPO_FIELD
)
747 && buffer
[i
= strlen (DISPO_FIELD
)] == ':') {
751 ct
->c_dispo
= add (buffer
+ i
+ 1, ct
->c_dispo
);
752 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
753 adios (NULL
, "end-of-file after %s: field in plaintext", DISPO_FIELD
);
761 adios (NULL
, "#-directive after %s: field in plaintext", DISPO_FIELD
);
769 if (headers
!= 1 || buffer
[0] != '\n')
775 if ((cp
= fgetstr (buffer
, sizeof(buffer
) - 1, in
)) == NULL
)
777 if (do_direct() && buffer
[0] == '#') {
780 if (buffer
[1] != '#')
782 for (cp
= (bp
= buffer
) + 1; *cp
; cp
++)
789 ct
->c_end
= ftell (out
);
792 /* parse content type */
793 if (get_ctinfo (content
, ct
, inlineD
) == NOTOK
)
796 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
797 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
799 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
803 * check type specified (possibly implicitly)
805 switch (ct
->c_type
= s2i
->si_val
) {
807 if (!strcasecmp (ci
->ci_subtype
, "rfc822")) {
808 ct
->c_encoding
= CE_7BIT
;
813 adios (NULL
, "it doesn't make sense to define an in-line %s content",
814 ct
->c_type
== CT_MESSAGE
? "message" : "multipart");
819 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
820 (*ct
->c_ctinitfnx
) (ct
);
825 fseek (in
, pos
, SEEK_SET
);
830 * If we've reached this point, the next line
831 * must be some type of explicit directive.
834 /* check if directive is external-type */
835 extrnal
= (buf
[1] == '@');
837 /* parse directive */
838 if (get_ctinfo (buf
+ (extrnal
? 2 : 1), ct
, 1) == NOTOK
)
841 /* check directive against the list of MIME types */
842 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
843 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
847 * Check if the directive specified a valid type.
848 * This will happen if it was one of the following forms:
855 adios (NULL
, "missing subtype in \"#%s\"", ci
->ci_type
);
857 switch (ct
->c_type
= s2i
->si_val
) {
859 adios (NULL
, "use \"#begin ... #end\" instead of \"#%s/%s\"",
860 ci
->ci_type
, ci
->ci_subtype
);
864 if (!strcasecmp (ci
->ci_subtype
, "partial"))
865 adios (NULL
, "sorry, \"#%s/%s\" isn't supported",
866 ci
->ci_type
, ci
->ci_subtype
);
867 if (!strcasecmp (ci
->ci_subtype
, "external-body"))
868 adios (NULL
, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
869 ci
->ci_type
, ci
->ci_subtype
);
872 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
873 ci
->ci_type
, ci
->ci_subtype
);
877 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
878 (*ct
->c_ctinitfnx
) (ct
);
883 * #@type/subtype (external types directive)
890 adios (NULL
, "need external information for \"#@%s/%s\"",
891 ci
->ci_type
, ci
->ci_subtype
);
894 snprintf (buffer
, sizeof(buffer
), "message/external-body; %s", ci
->ci_magic
);
899 * Since we are using the current Content structure to
900 * hold information about the type of the external
901 * reference, we need to create another Content structure
902 * for the message/external-body to wrap it in.
905 init_decoded_content(ct
, infilename
);
907 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
909 ct
->c_type
= CT_MESSAGE
;
910 ct
->c_subtype
= MESSAGE_EXTERNAL
;
913 ct
->c_ctparams
= (void *) e
;
919 if (params_external (ct
, 1) == NOTOK
)
925 /* Handle [file] argument */
927 /* check if specifies command to execute */
928 if (*ci
->ci_magic
== '|' || *ci
->ci_magic
== '!') {
929 for (cp
= ci
->ci_magic
+ 1; isspace ((unsigned char) *cp
); cp
++)
932 adios (NULL
, "empty pipe command for #%s directive", ci
->ci_type
);
937 /* record filename of decoded contents */
938 ce
->ce_file
= ci
->ci_magic
;
939 if (access (ce
->ce_file
, R_OK
) == NOTOK
)
940 adios ("reading", "unable to access %s for", ce
->ce_file
);
941 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
942 ct
->c_end
= (long) st
.st_size
;
949 * No [file] argument, so check profile for
950 * method to compose content.
952 cp
= context_find_by_type ("compose", ci
->ci_type
, ci
->ci_subtype
);
954 content_error (NULL
, ct
, "don't know how to compose content");
957 ci
->ci_magic
= add (cp
, NULL
);
962 adios (NULL
, "external definition not allowed for \"#%s\"", ci
->ci_type
);
966 * #forw [+folder] [msgs]
968 if (!strcasecmp (ci
->ci_type
, "forw")) {
970 char *folder
, *arguments
[MAXARGS
];
974 ap
= brkstring (ci
->ci_magic
, " ", "\n");
975 copyip (ap
, arguments
, MAXARGS
);
977 arguments
[0] = "cur";
982 /* search the arguments for a folder name */
983 for (ap
= arguments
; *ap
; ap
++) {
985 if (*cp
== '+' || *cp
== '@') {
987 adios (NULL
, "only one folder per #forw directive");
989 folder
= pluspath (cp
);
993 /* else, use the current folder */
995 folder
= add (getfolder (1), NULL
);
997 if (!(mp
= folder_read (folder
, 0)))
998 adios (NULL
, "unable to read folder %s", folder
);
999 for (ap
= arguments
; *ap
; ap
++) {
1001 if (*cp
!= '+' && *cp
!= '@')
1002 if (!m_convert (mp
, cp
))
1009 * If there is more than one message to include, make this
1010 * a content of type "multipart/digest" and insert each message
1011 * as a subpart. If there is only one message, then make this
1012 * a content of type "message/rfc822".
1014 if (mp
->numsel
> 1) {
1015 /* we are forwarding multiple messages */
1016 if (get_ctinfo ("multipart/digest", ct
, 0) == NOTOK
)
1018 ct
->c_type
= CT_MULTIPART
;
1019 ct
->c_subtype
= MULTI_DIGEST
;
1022 ct
->c_ctparams
= (void *) m
;
1025 for (msgnum
= mp
->lowsel
; msgnum
<= mp
->hghsel
; msgnum
++) {
1026 if (is_selected(mp
, msgnum
)) {
1032 init_decoded_content (p
, infilename
);
1034 if (get_ctinfo ("message/rfc822", p
, 0) == NOTOK
)
1036 p
->c_type
= CT_MESSAGE
;
1037 p
->c_subtype
= MESSAGE_RFC822
;
1039 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1040 pe
->ce_file
= add (buffer
, NULL
);
1041 if (listsw
&& stat (pe
->ce_file
, &st
) != NOTOK
)
1042 p
->c_end
= (long) st
.st_size
;
1046 pp
= &part
->mp_next
;
1051 /* we are forwarding one message */
1052 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
1054 ct
->c_type
= CT_MESSAGE
;
1055 ct
->c_subtype
= MESSAGE_RFC822
;
1057 msgnum
= mp
->lowsel
;
1058 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1059 ce
->ce_file
= add (buffer
, NULL
);
1060 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
1061 ct
->c_end
= (long) st
.st_size
;
1064 folder_free (mp
); /* free folder/message structure */
1071 if (!strcasecmp (ci
->ci_type
, "end")) {
1078 * #begin [ alternative | parallel ]
1080 if (!strcasecmp (ci
->ci_type
, "begin")) {
1081 if (!ci
->ci_magic
) {
1083 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1084 } else if (!strcasecmp (ci
->ci_magic
, "alternative")) {
1085 vrsn
= MULTI_ALTERNATE
;
1086 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1087 } else if (!strcasecmp (ci
->ci_magic
, "parallel")) {
1088 vrsn
= MULTI_PARALLEL
;
1089 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1090 } else if (uprf (ci
->ci_magic
, "digest")) {
1093 vrsn
= MULTI_UNKNOWN
;
1098 snprintf (buffer
, sizeof(buffer
), "multipart/%s", cp
);
1099 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
1101 ct
->c_type
= CT_MULTIPART
;
1102 ct
->c_subtype
= vrsn
;
1105 ct
->c_ctparams
= (void *) m
;
1108 while (fgetstr (buffer
, sizeof(buffer
) - 1, in
)) {
1112 if (user_content (in
, buffer
, &p
, infilename
) == DONE
) {
1114 adios (NULL
, "empty \"#begin ... #end\" sequence");
1122 pp
= &part
->mp_next
;
1125 admonish (NULL
, "premature end-of-file, missing #end");
1132 adios (NULL
, "unknown directive \"#%s\"", ci
->ci_type
);
1133 return NOTOK
; /* NOT REACHED */
1138 set_id (CT ct
, int top
)
1140 char contentid
[BUFSIZ
];
1142 static time_t clock
= 0;
1143 static char *msgfmt
;
1147 snprintf (contentid
, sizeof(contentid
), "%s\n", message_id (clock
, 1));
1149 msgfmt
= mh_xstrdup(contentid
);
1151 snprintf (contentid
, sizeof(contentid
), msgfmt
, top
? 0 : ++partno
);
1152 ct
->c_id
= mh_xstrdup(contentid
);
1157 * Fill out, or expand the various contents in the composition
1158 * draft. Read-in any necessary files. Parse and execute any
1159 * commands specified by profile composition strings.
1163 compose_content (CT ct
, int verbose
)
1165 CE ce
= &ct
->c_cefile
;
1167 switch (ct
->c_type
) {
1172 char partnam
[BUFSIZ
];
1173 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1177 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1178 pp
= partnam
+ strlen (partnam
);
1183 /* first, we call compose_content on all the subparts */
1184 for (part
= m
->mp_parts
, partnum
= 1; part
; part
= part
->mp_next
, partnum
++) {
1185 CT p
= part
->mp_part
;
1187 sprintf (pp
, "%d", partnum
);
1188 p
->c_partno
= add (partnam
, NULL
);
1189 if (compose_content (p
, verbose
) == NOTOK
)
1194 * If the -rfc934mode switch is given, then check all
1195 * the subparts of a multipart/digest. If they are all
1196 * message/rfc822, then mark this content and all
1197 * subparts with the rfc934 compatibility mode flag.
1199 if (rfc934sw
&& ct
->c_subtype
== MULTI_DIGEST
) {
1202 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1203 CT p
= part
->mp_part
;
1205 if (p
->c_subtype
!= MESSAGE_RFC822
) {
1210 ct
->c_rfc934
= is934
;
1211 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1212 CT p
= part
->mp_part
;
1214 if ((p
->c_rfc934
= is934
))
1220 ct
->c_end
= (partnum
= strlen (prefix
) + 2) + 2;
1224 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
)
1225 ct
->c_end
+= part
->mp_part
->c_end
+ partnum
;
1231 /* Nothing to do for type message */
1235 * Discrete types (text/application/audio/image/video)
1240 int i
, xstdout
, len
, buflen
;
1242 char *vec
[4], buffer
[BUFSIZ
];
1244 CI ci
= &ct
->c_ctinfo
;
1247 if (!(cp
= ci
->ci_magic
))
1248 adios (NULL
, "internal error(5)");
1250 if ((tfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1251 adios("mhbuildsbr", "unable to create temporary file in %s",
1254 ce
->ce_file
= add (tfile
, NULL
);
1259 /* Get buffer ready to go */
1262 buflen
= sizeof(buffer
);
1265 * Parse composition string into buffer
1267 for ( ; *cp
; cp
++) {
1272 /* insert parameters from directive */
1276 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1277 snprintf (bp
, buflen
, "%s%s=\"%s\"", s
,
1278 pm
->pm_name
, get_param_value(pm
, '?'));
1288 /* %f, and stdout is not-redirected */
1294 * insert temporary filename where
1295 * content should be written
1297 snprintf (bp
, buflen
, "%s", ce
->ce_file
);
1301 /* insert content subtype */
1302 strncpy (bp
, ci
->ci_subtype
, buflen
);
1306 /* insert character % */
1327 printf ("composing content %s/%s from command\n\t%s\n",
1328 ci
->ci_type
, ci
->ci_subtype
, buffer
);
1330 fflush (stdout
); /* not sure if need for -noverbose */
1337 if ((out
= fopen (ce
->ce_file
, "w")) == NULL
)
1338 adios (ce
->ce_file
, "unable to open for writing");
1340 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
> 5; i
++)
1344 adios ("fork", "unable to fork");
1349 dup2 (fileno (out
), 1);
1350 close (fileno (out
));
1351 execvp ("/bin/sh", vec
);
1352 fprintf (stderr
, "unable to exec ");
1359 if (pidXwait(child_id
, NULL
))
1365 /* Check size of file */
1366 if (listsw
&& ct
->c_end
== 0L) {
1369 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1370 ct
->c_end
= (long) st
.st_size
;
1382 * 1) choose a transfer encoding.
1383 * 2) check for clashes with multipart boundary string.
1384 * 3) for text content, figure out which character set is being used.
1386 * If there is a clash with one of the contents and the multipart boundary,
1387 * this function will exit with NOTOK. This will cause the scanning process
1388 * to be repeated with a different multipart boundary. It is possible
1389 * (although highly unlikely) that this scan will be repeated multiple times.
1393 scan_content (CT ct
, size_t maxunencoded
)
1396 int check8bit
= 0, contains8bit
= 0; /* check if contains 8bit data */
1397 int checknul
= 0, containsnul
= 0; /* check if contains NULs */
1398 int checklinelen
= 0, linelen
= 0; /* check for long lines */
1399 int checkllinelen
= 0; /* check for extra-long lines */
1400 int checkboundary
= 0, boundaryclash
= 0; /* check if clashes with multipart boundary */
1401 int checklinespace
= 0, linespace
= 0; /* check if any line ends with space */
1406 struct text
*t
= NULL
;
1408 CE ce
= &ct
->c_cefile
;
1411 * handle multipart by scanning all subparts
1412 * and then checking their encoding.
1414 if (ct
->c_type
== CT_MULTIPART
) {
1415 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1418 /* initially mark the domain of enclosing multipart as 7bit */
1419 ct
->c_encoding
= CE_7BIT
;
1421 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1422 CT p
= part
->mp_part
;
1424 if (scan_content (p
, maxunencoded
) == NOTOK
) /* choose encoding for subpart */
1427 /* if necessary, enlarge encoding for enclosing multipart */
1428 if (p
->c_encoding
== CE_BINARY
)
1429 ct
->c_encoding
= CE_BINARY
;
1430 if (p
->c_encoding
== CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
)
1431 ct
->c_encoding
= CE_8BIT
;
1438 * Decide what to check while scanning this content. Note that
1439 * for text content we always check for 8bit characters if the
1440 * charset is unspecified, because that controls whether or not the
1441 * character set is us-ascii or retrieved from the locale. And
1442 * we check even if the charset is specified, to allow setting
1443 * the proper Content-Transfer-Encoding.
1446 if (ct
->c_type
== CT_TEXT
) {
1447 t
= (struct text
*) ct
->c_ctparams
;
1448 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
1454 switch (ct
->c_reqencoding
) {
1465 /* Use the default rules based on content-type */
1466 switch (ct
->c_type
) {
1470 if (ct
->c_subtype
== TEXT_PLAIN
) {
1477 case CT_APPLICATION
:
1489 /* don't check anything for message/external */
1490 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1503 * Don't check anything for these types,
1504 * since we are forcing use of base64, unless
1505 * the content-type was specified by a mhbuild directive.
1516 * Scan the unencoded content
1518 if (check8bit
|| checklinelen
|| checklinespace
|| checkboundary
||
1519 checkllinelen
|| checknul
) {
1520 if ((in
= fopen (ce
->ce_file
, "r")) == NULL
)
1521 adios (ce
->ce_file
, "unable to open for reading");
1522 prefix_len
= strlen (prefix
);
1524 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1526 * Check for 8bit and NUL data.
1528 for (cp
= bufp
; (check8bit
|| checknul
) &&
1529 cp
< bufp
+ gotlen
; cp
++) {
1530 if (!isascii ((unsigned char) *cp
)) {
1532 check8bit
= 0; /* no need to keep checking */
1536 checknul
= 0; /* no need to keep checking */
1541 * Check line length.
1543 if (checklinelen
&& ((size_t)gotlen
> maxunencoded
+ 1)) {
1545 checklinelen
= 0; /* no need to keep checking */
1549 * RFC 5322 specifies that a message cannot contain a line
1550 * greater than 998 characters (excluding the CRLF). If we
1551 * get one of those lines and linelen is NOT set, then abort.
1554 if (checkllinelen
&& !linelen
&&
1555 (gotlen
> MAXLONGLINE
+ 1)) {
1556 adios(NULL
, "Line in content exceeds maximum line limit (%d)",
1561 * Check if line ends with a space.
1563 if (checklinespace
&& (cp
= bufp
+ gotlen
- 2) > bufp
&&
1564 isspace ((unsigned char) *cp
)) {
1566 checklinespace
= 0; /* no need to keep checking */
1570 * Check if content contains a line that clashes
1571 * with our standard boundary for multipart messages.
1573 if (checkboundary
&& bufp
[0] == '-' && bufp
[1] == '-') {
1574 for (cp
= bufp
+ gotlen
- 1; cp
>= bufp
; cp
--)
1575 if (!isspace ((unsigned char) *cp
))
1578 if (!strncmp(bufp
+ 2, prefix
, prefix_len
) &&
1579 isdigit((unsigned char) bufp
[2 + prefix_len
])) {
1581 checkboundary
= 0; /* no need to keep checking */
1590 * If the content is text and didn't specify a character set,
1591 * we need to figure out which one was used.
1593 set_charset (ct
, contains8bit
);
1596 * Decide which transfer encoding to use.
1599 if (ct
->c_reqencoding
!= CE_UNKNOWN
)
1600 ct
->c_encoding
= ct
->c_reqencoding
;
1602 int wants_q_p
= (containsnul
|| linelen
|| linespace
|| checksw
);
1604 switch (ct
->c_type
) {
1607 ct
->c_encoding
= CE_QUOTED
;
1608 else if (contains8bit
)
1609 ct
->c_encoding
= CE_8BIT
;
1611 ct
->c_encoding
= CE_7BIT
;
1615 case CT_APPLICATION
:
1616 /* For application type, use base64, except when postscript */
1617 if (wants_q_p
|| contains8bit
) {
1618 if (ct
->c_subtype
== APPLICATION_POSTSCRIPT
)
1619 ct
->c_encoding
= CE_QUOTED
; /* historical */
1621 ct
->c_encoding
= CE_BASE64
;
1623 ct
->c_encoding
= CE_7BIT
;
1628 ct
->c_encoding
= contains8bit
? CE_8BIT
: CE_7BIT
;
1634 /* For audio, image, and video contents, just use base64 */
1635 ct
->c_encoding
= CE_BASE64
;
1640 return (boundaryclash
? NOTOK
: OK
);
1645 * Scan the content structures, and build header
1646 * fields that will need to be output into the
1651 build_headers (CT ct
, int header_encoding
)
1653 int cc
, mailbody
, extbody
, len
;
1654 char *np
, *vp
, buffer
[BUFSIZ
];
1655 CI ci
= &ct
->c_ctinfo
;
1658 * If message is type multipart, then add the multipart
1659 * boundary to the list of attribute/value pairs.
1661 if (ct
->c_type
== CT_MULTIPART
) {
1662 static int level
= 0; /* store nesting level */
1664 snprintf (buffer
, sizeof(buffer
), "%s%d", prefix
, level
++);
1665 add_param(&ci
->ci_first_pm
, &ci
->ci_last_pm
, "boundary", buffer
, 0);
1669 * Skip the output of Content-Type, parameters, content
1670 * description and disposition, and Content-ID if the
1671 * content is of type "message" and the rfc934 compatibility
1672 * flag is set (which means we are inside multipart/digest
1673 * and the switch -rfc934mode was given).
1675 if (ct
->c_type
== CT_MESSAGE
&& ct
->c_rfc934
)
1679 * output the content type and subtype
1681 np
= add (TYPE_FIELD
, NULL
);
1682 vp
= concat (" ", ci
->ci_type
, "/", ci
->ci_subtype
, NULL
);
1684 /* keep track of length of line */
1685 len
= strlen (TYPE_FIELD
) + strlen (ci
->ci_type
)
1686 + strlen (ci
->ci_subtype
) + 3;
1688 extbody
= ct
->c_type
== CT_MESSAGE
&& ct
->c_subtype
== MESSAGE_EXTERNAL
;
1689 mailbody
= extbody
&& ((struct exbody
*) ct
->c_ctparams
)->eb_body
;
1692 * Append the attribute/value pairs to
1693 * the end of the Content-Type line.
1696 if (ci
->ci_first_pm
) {
1697 char *s
= output_params(len
, ci
->ci_first_pm
, &len
, mailbody
);
1700 adios(NULL
, "Internal error: failed outputting Content-Type "
1708 * Append any RFC-822 comment to the end of
1709 * the Content-Type line.
1711 if (ci
->ci_comment
) {
1712 snprintf (buffer
, sizeof(buffer
), "(%s)", ci
->ci_comment
);
1713 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
1714 vp
= add ("\n\t", vp
);
1720 vp
= add (buffer
, vp
);
1723 vp
= add ("\n", vp
);
1724 add_header (ct
, np
, vp
);
1727 * output the Content-ID, unless disabled by -nocontentid
1729 if (contentidsw
&& ct
->c_id
) {
1730 np
= add (ID_FIELD
, NULL
);
1731 vp
= concat (" ", ct
->c_id
, NULL
);
1732 add_header (ct
, np
, vp
);
1735 * output the Content-Description
1738 np
= add (DESCR_FIELD
, NULL
);
1739 vp
= concat (" ", ct
->c_descr
, NULL
);
1740 if (header_encoding
!= CE_8BIT
) {
1741 if (encode_rfc2047(DESCR_FIELD
, &vp
, header_encoding
, NULL
)) {
1742 adios(NULL
, "Unable to encode %s header", DESCR_FIELD
);
1745 add_header (ct
, np
, vp
);
1749 * output the Content-Disposition. If it's NULL but c_dispo_type is
1750 * set, then we need to build it.
1753 np
= add (DISPO_FIELD
, NULL
);
1754 vp
= concat (" ", ct
->c_dispo
, NULL
);
1755 add_header (ct
, np
, vp
);
1756 } else if (ct
->c_dispo_type
) {
1757 vp
= concat (" ", ct
->c_dispo_type
, NULL
);
1758 len
= strlen(DISPO_FIELD
) + strlen(vp
) + 1;
1759 np
= output_params(len
, ct
->c_dispo_first
, NULL
, 0);
1763 add_header (ct
, mh_xstrdup(DISPO_FIELD
), vp
);
1768 * If this is the internal content structure for a
1769 * "message/external", then we are done with the
1770 * headers (since it has no body).
1776 * output the Content-MD5
1779 np
= add (MD5_FIELD
, NULL
);
1780 vp
= calculate_digest (ct
, (ct
->c_encoding
== CE_QUOTED
) ? 1 : 0);
1781 add_header (ct
, np
, vp
);
1785 * output the Content-Transfer-Encoding
1786 * If using EAI and message body is 7-bit, force 8-bit C-T-E.
1788 if (header_encoding
== CE_8BIT
&& ct
->c_encoding
== CE_7BIT
) {
1789 ct
->c_encoding
= CE_8BIT
;
1792 switch (ct
->c_encoding
) {
1794 /* Nothing to output */
1798 np
= add (ENCODING_FIELD
, NULL
);
1799 vp
= concat (" ", "8bit", "\n", NULL
);
1800 add_header (ct
, np
, vp
);
1804 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1805 adios (NULL
, "internal error, invalid encoding");
1807 np
= add (ENCODING_FIELD
, NULL
);
1808 vp
= concat (" ", "quoted-printable", "\n", NULL
);
1809 add_header (ct
, np
, vp
);
1813 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1814 adios (NULL
, "internal error, invalid encoding");
1816 np
= add (ENCODING_FIELD
, NULL
);
1817 vp
= concat (" ", "base64", "\n", NULL
);
1818 add_header (ct
, np
, vp
);
1822 if (ct
->c_type
== CT_MESSAGE
)
1823 adios (NULL
, "internal error, invalid encoding");
1825 np
= add (ENCODING_FIELD
, NULL
);
1826 vp
= concat (" ", "binary", "\n", NULL
);
1827 add_header (ct
, np
, vp
);
1831 adios (NULL
, "unknown transfer encoding in content");
1836 * Additional content specific header processing
1838 switch (ct
->c_type
) {
1841 struct multipart
*m
;
1844 m
= (struct multipart
*) ct
->c_ctparams
;
1845 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1849 build_headers (p
, header_encoding
);
1855 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1858 e
= (struct exbody
*) ct
->c_ctparams
;
1859 build_headers (e
->eb_content
, header_encoding
);
1872 static char nib2b64
[0x40+1] =
1873 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1876 calculate_digest (CT ct
, int asciiP
)
1881 unsigned char digest
[16];
1882 unsigned char outbuf
[25];
1884 CE ce
= &ct
->c_cefile
;
1885 char *infilename
= ce
->ce_file
? ce
->ce_file
: ct
->c_file
;
1889 if ((in
= fopen (infilename
, "r")) == NULL
)
1890 adios (infilename
, "unable to open for reading");
1892 /* Initialize md5 context */
1893 MD5Init (&mdContext
);
1895 /* calculate md5 message digest */
1900 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1903 cp
= bufp
+ gotlen
- 1;
1904 if ((c
= *cp
) == '\n')
1907 MD5Update (&mdContext
, (unsigned char *) bufp
,
1908 (unsigned int) gotlen
);
1911 MD5Update (&mdContext
, (unsigned char *) "\r\n", 2);
1914 char buffer
[BUFSIZ
];
1915 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), in
)) > 0)
1916 MD5Update (&mdContext
, (unsigned char *) buffer
, (unsigned int) cc
);
1919 /* md5 finalization. Write digest and zero md5 context */
1920 MD5Final (digest
, &mdContext
);
1925 /* print debugging info */
1929 fprintf (stderr
, "MD5 digest=");
1930 for (ep
= (dp
= digest
) + sizeof(digest
) / sizeof(digest
[0]);
1932 fprintf (stderr
, "%02x", *dp
& 0xff);
1933 fprintf (stderr
, "\n");
1936 /* encode the digest using base64 */
1937 for (dp
= digest
, op
= (char *) outbuf
,
1938 cc
= sizeof(digest
) / sizeof(digest
[0]);
1939 cc
> 0; cc
-= 3, op
+= 4) {
1943 bits
= (*dp
++ & 0xff) << 16;
1945 bits
|= (*dp
++ & 0xff) << 8;
1947 bits
|= *dp
++ & 0xff;
1950 for (bp
= op
+ 4; bp
> op
; bits
>>= 6)
1951 *--bp
= nib2b64
[bits
& 0x3f];
1959 /* null terminate string */
1962 /* now make copy and return string */
1963 vp
= concat (" ", outbuf
, "\n", NULL
);
1968 * Set things up for the content structure for file "filename" that
1973 setup_attach_content(CT ct
, char *filename
)
1975 char *type
, *simplename
= r1bindex(filename
, '/');
1976 struct str2init
*s2i
;
1979 if (! (type
= mime_type(filename
))) {
1980 adios(NULL
, "Unable to determine MIME type of \"%s\"", filename
);
1984 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1985 * since we're just feeding it a MIME type we have to add those ourself.
1986 * Map that to a valid content-type label and call any initialization
1990 if (get_ctinfo(type
, ct
, 0) == NOTOK
)
1995 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
1996 if (strcasecmp(ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0)
1998 if (!s2i
->si_key
&& !uprf(ct
->c_ctinfo
.ci_type
, "X-"))
2002 * Make sure the type isn't incompatible with what we can handle
2005 switch (ct
->c_type
= s2i
->si_val
) {
2007 adios (NULL
, "multipart types must be specified by mhbuild directives");
2011 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "partial") == 0)
2012 adios(NULL
, "Sorry, %s/%s isn't supported", ct
->c_ctinfo
.ci_type
,
2013 ct
->c_ctinfo
.ci_subtype
);
2014 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "external-body") == 0)
2015 adios(NULL
, "external-body messages must be specified "
2016 "by mhbuild directives");
2021 * This sets the subtype, if it's significant
2023 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
2024 (*ct
->c_ctinitfnx
)(ct
);
2029 * Feed in a few attributes; specifically, the name attribute, the
2030 * content-description, and the content-disposition.
2033 for (pm
= ct
->c_ctinfo
.ci_first_pm
; pm
; pm
= pm
->pm_next
) {
2034 if (strcasecmp(pm
->pm_name
, "name") == 0) {
2035 mh_xfree(pm
->pm_value
);
2036 pm
->pm_value
= mh_xstrdup(simplename
);
2042 add_param(&ct
->c_ctinfo
.ci_first_pm
, &ct
->c_ctinfo
.ci_last_pm
,
2043 "name", simplename
, 0);
2045 ct
->c_descr
= mh_xstrdup(simplename
);
2046 ct
->c_descr
= add("\n", ct
->c_descr
);
2047 ct
->c_cefile
.ce_file
= mh_xstrdup(filename
);
2049 set_disposition (ct
);
2051 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename", simplename
, 0);
2055 * If disposition type hasn't already been set in ct:
2056 * Look for mhbuild-disposition-<type>/<subtype> entry
2057 * that specifies Content-Disposition type. Only
2058 * 'attachment' and 'inline' are allowed. Default to
2062 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 admonish (NULL
, "configuration problem: %s-disposition-%s%s%s "
2070 "specifies '%s' but only 'attachment' and 'inline' are "
2071 "allowed", invo_name
,
2072 ct
->c_ctinfo
.ci_type
,
2073 ct
->c_ctinfo
.ci_subtype
? "/" : "",
2074 ct
->c_ctinfo
.ci_subtype
? 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
) {
2093 if (ct
->c_type
== CT_TEXT
) {
2096 if (ct
->c_ctparams
== NULL
) {
2099 t
->tx_charset
= CHARSET_UNSPECIFIED
;
2101 t
= (struct text
*) ct
->c_ctparams
;
2104 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
2105 CI ci
= &ct
->c_ctinfo
;
2106 char *eightbitcharset
= write_charset_8bit();
2107 char *charset
= contains8bit
? eightbitcharset
: "us-ascii";
2109 if (contains8bit
== 1 &&
2110 strcasecmp (eightbitcharset
, "US-ASCII") == 0) {
2111 adios (NULL
, "Text content contains 8 bit characters, but "
2112 "character set is US-ASCII");
2115 add_param (&ci
->ci_first_pm
, &ci
->ci_last_pm
, "charset", charset
,
2118 t
->tx_charset
= CHARSET_SPECIFIED
;
2125 * Look at all of the replied-to message parts and expand any that
2126 * are matched by a pseudoheader. Except don't descend into
2130 expand_pseudoheaders (CT ct
, struct multipart
*m
, const char *infile
,
2131 const convert_list
*convert_head
) {
2132 /* text_plain_ct is used to concatenate all of the text/plain
2133 replies into one part, instead of having each one in a separate
2135 CT text_plain_ct
= NULL
;
2137 switch (ct
->c_type
) {
2138 case CT_MULTIPART
: {
2139 struct multipart
*mp
= (struct multipart
*) ct
->c_ctparams
;
2142 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
2145 /* The parts are in descending priority order (defined by
2146 RFC 2046 Sec. 5.1.4) because they were reversed by
2147 parse_mime (). So, stop looking for matches with
2148 immediate subparts after the first match of an
2150 for (part
= mp
->mp_parts
; ! matched
&& part
; part
= part
->mp_next
) {
2151 char *type_subtype
=
2152 concat (part
->mp_part
->c_ctinfo
.ci_type
, "/",
2153 part
->mp_part
->c_ctinfo
.ci_subtype
, NULL
);
2155 if (part
->mp_part
->c_type
== CT_MULTIPART
) {
2156 expand_pseudoheaders (part
->mp_part
, m
, infile
,
2159 const convert_list
*c
;
2161 for (c
= convert_head
; c
; c
= c
->next
) {
2162 if (! strcasecmp (type_subtype
, c
->type
)) {
2163 expand_pseudoheader (part
->mp_part
, &text_plain_ct
,
2165 c
->type
, c
->argstring
);
2171 free (type_subtype
);
2174 for (part
= mp
->mp_parts
; part
; part
= part
->mp_next
) {
2175 expand_pseudoheaders (part
->mp_part
, m
, infile
, convert_head
);
2182 char *type_subtype
=
2183 concat (ct
->c_ctinfo
.ci_type
, "/", ct
->c_ctinfo
.ci_subtype
,
2185 const convert_list
*c
;
2187 for (c
= convert_head
; c
; c
= c
->next
) {
2188 if (! strcasecmp (type_subtype
, c
->type
)) {
2189 expand_pseudoheader (ct
, &text_plain_ct
, m
, infile
, c
->type
,
2194 free (type_subtype
);
2202 * Expand a single pseudoheader. It's for the specified type.
2205 expand_pseudoheader (CT ct
, CT
*text_plain_ct
, struct multipart
*m
,
2206 const char *infile
, const char *type
,
2207 const char *argstring
) {
2209 FILE *reply_fp
= NULL
;
2210 char *convert
, *type_p
, *subtype_p
;
2211 char *convert_command
;
2212 char *charset
= NULL
;
2214 struct str2init
*s2i
;
2220 type_p
= getcpy (type
);
2221 if ((subtype_p
= strchr (type_p
, '/'))) {
2222 *subtype_p
++ = '\0';
2223 convert
= context_find_by_type ("convert", type_p
, subtype_p
);
2226 type_p
= concat ("mhbuild-convert-", type
, NULL
);
2227 convert
= context_find (type_p
);
2232 /* No mhbuild-convert- entry in mhn.defaults or profile
2236 /* reply_file is used to pass the output of the convert. */
2237 reply_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2239 concat (convert
, " ", argstring
? argstring
: "", " >", reply_file
,
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 admonish (NULL
, "store of %s content failed", 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
) {
2259 "failed to extract headers from convert output in %s",
2264 /* For text content only, see if it is 8-bit text. */
2265 if (reply_ct
->c_type
== CT_TEXT
) {
2268 if ((fd
= open (reply_file
, O_RDONLY
)) == NOTOK
||
2269 scan_input (fd
, &eightbit
) == NOTOK
) {
2271 admonish (NULL
, "failed to read %s", reply_file
);
2277 /* This sets reply_ct->c_ctparams, and reply_ct->c_termproc if the
2278 charset can't be handled natively. */
2279 for (s2i
= str2cts
; s2i
->si_key
; s2i
++) {
2280 if (strcasecmp(reply_ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0) {
2285 if ((reply_ct
->c_ctinitfnx
= s2i
->si_init
)) {
2286 (*reply_ct
->c_ctinitfnx
)(reply_ct
);
2290 get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1))) {
2291 /* The reply Content-Type had the charset. */
2294 set_charset (reply_ct
, -1);
2295 charset
= get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1);
2296 if (reply_ct
->c_reqencoding
== CE_UNKNOWN
&&
2297 reply_ct
->c_type
== CT_TEXT
) {
2298 /* Assume that 8bit is sufficient (for text). In other words,
2299 don't allow it to be encoded as quoted printable if lines
2300 are too long. This also sidesteps the check for whether
2301 it needs to be encoded as binary; instead, it relies on
2302 the applicable mhbuild-convert-text directive to ensure
2303 that the resultant text is not binary. */
2304 reply_ct
->c_reqencoding
= eightbit
? CE_8BIT
: CE_7BIT
;
2308 /* Concatenate text/plain parts. */
2309 if (reply_ct
->c_type
== CT_TEXT
&&
2310 reply_ct
->c_subtype
== TEXT_PLAIN
) {
2311 if (! *text_plain_ct
&& m
->mp_parts
&& m
->mp_parts
->mp_part
&&
2312 m
->mp_parts
->mp_part
->c_type
== CT_TEXT
&&
2313 m
->mp_parts
->mp_part
->c_subtype
== TEXT_PLAIN
) {
2314 *text_plain_ct
= m
->mp_parts
->mp_part
;
2315 /* Make sure that the charset is set in the text/plain
2317 set_charset (*text_plain_ct
, -1);
2318 if ((*text_plain_ct
)->c_reqencoding
== CE_UNKNOWN
) {
2319 /* Assume that 8bit is sufficient (for text). In other words,
2320 don't allow it to be encoded as quoted printable if lines
2321 are too long. This also sidesteps the check for whether
2322 it needs to be encoded as binary; instead, it relies on
2323 the applicable mhbuild-convert-text directive to ensure
2324 that the resultant text is not binary. */
2325 (*text_plain_ct
)->c_reqencoding
=
2326 eightbit
? CE_8BIT
: CE_7BIT
;
2330 if (*text_plain_ct
) {
2331 /* Only concatenate if the charsets are identical. */
2332 char *text_plain_ct_charset
=
2333 get_param ((*text_plain_ct
)->c_ctinfo
.ci_first_pm
, "charset",
2336 if (strcasecmp (text_plain_ct_charset
, charset
) == 0) {
2337 /* Append this text/plain reply to the first one.
2338 If there's a problem anywhere along the way,
2339 instead attach it is a separate part. */
2340 int text_plain_reply
=
2341 open ((*text_plain_ct
)->c_cefile
.ce_file
,
2342 O_WRONLY
| O_APPEND
);
2343 int addl_reply
= open (reply_file
, O_RDONLY
);
2345 if (text_plain_reply
!= NOTOK
&& addl_reply
!= NOTOK
) {
2346 /* Insert blank line before each addl part. */
2347 /* It would be nice not to do this for the first one. */
2348 if (write (text_plain_reply
, "\n", 1) == 1) {
2349 /* Copy the text from the new reply and
2350 then free its Content struct. */
2351 cpydata (addl_reply
, text_plain_reply
,
2352 (*text_plain_ct
)->c_cefile
.ce_file
,
2354 if (close (text_plain_reply
) == OK
&&
2355 close (addl_reply
) == OK
) {
2356 /* If appended text needed 8-bit but first text didn't,
2357 propagate the 8-bit indication. */
2358 if ((*text_plain_ct
)->c_reqencoding
== CE_7BIT
&&
2359 reply_ct
->c_reqencoding
== CE_8BIT
) {
2360 (*text_plain_ct
)->c_reqencoding
= CE_8BIT
;
2363 if (reply_fp
) { fclose (reply_fp
); }
2365 free_content (reply_ct
);
2372 *text_plain_ct
= reply_ct
;
2376 reply_ct
->c_cefile
.ce_file
= reply_file
;
2377 reply_ct
->c_cefile
.ce_fp
= reply_fp
;
2378 reply_ct
->c_cefile
.ce_unlink
= 1;
2380 /* Attach the new part to the parent multipart/mixed, "m". */
2382 part
->mp_part
= reply_ct
;
2386 for (p
= m
->mp_parts
; p
&& p
->mp_next
; p
= p
->mp_next
) { continue; }
2394 /* Extract any Content-Type header from beginning of convert output. */
2396 extract_headers (CT ct
, char *reply_file
, FILE **reply_fp
) {
2397 char *buffer
= NULL
, *cp
, *end_of_header
;
2398 int found_header
= 0;
2399 struct stat statbuf
;
2401 /* Read the convert reply from the file to memory. */
2402 if (stat (reply_file
, &statbuf
) == NOTOK
) {
2403 admonish (reply_file
, "failed to stat");
2404 goto failed_to_extract_ct
;
2407 buffer
= mh_xmalloc (statbuf
.st_size
+ 1);
2409 if ((*reply_fp
= fopen (reply_file
, "r+")) == NULL
||
2410 fread (buffer
, 1, (size_t) statbuf
.st_size
, *reply_fp
) <
2411 (size_t) statbuf
.st_size
) {
2412 admonish (reply_file
, "failed to read");
2413 goto failed_to_extract_ct
;
2415 buffer
[statbuf
.st_size
] = '\0';
2417 /* Look for a header in the convert reply. */
2418 if (strncasecmp (buffer
, TYPE_FIELD
, strlen (TYPE_FIELD
)) == 0 &&
2419 buffer
[strlen (TYPE_FIELD
)] == ':') {
2420 if ((end_of_header
= strstr (buffer
, "\r\n\r\n"))) {
2423 } else if ((end_of_header
= strstr (buffer
, "\n\n"))) {
2435 /* Truncate buffer to just the C-T. */
2436 *end_of_header
= '\0';
2437 n
= strlen (buffer
);
2439 if (get_ctinfo (buffer
+ 14, ct
, 0) != OK
) {
2440 admonish (NULL
, "unable to get content info for reply");
2441 goto failed_to_extract_ct
;
2444 /* Hack. Use parse_mime() to detect the type/subtype of the
2445 reply, which we'll use below. */
2446 tmp_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2447 if ((tmp_f
= fopen (tmp_file
, "w")) &&
2448 fwrite (buffer
, 1, n
, tmp_f
) == n
) {
2451 goto failed_to_extract_ct
;
2453 tmp_ct
= parse_mime (tmp_file
);
2456 /* The type and subtype were detected from the reply
2457 using parse_mime() above. */
2458 ct
->c_type
= tmp_ct
->c_type
;
2459 ct
->c_subtype
= tmp_ct
->c_subtype
;
2460 free_content (tmp_ct
);
2465 /* Rewrite the content without the header. */
2466 cp
= end_of_header
+ 1;
2469 if (fwrite (cp
, 1, statbuf
.st_size
- (cp
- buffer
), *reply_fp
) <
2470 (size_t) (statbuf
.st_size
- (cp
- buffer
))) {
2471 admonish (reply_file
, "failed to write");
2472 goto failed_to_extract_ct
;
2475 if (ftruncate (fileno (*reply_fp
), statbuf
.st_size
- (cp
- buffer
)) !=
2477 advise (reply_file
, "ftruncate");
2478 goto failed_to_extract_ct
;
2481 /* No header section, assume the reply is text/plain. */
2482 ct
->c_type
= CT_TEXT
;
2483 ct
->c_subtype
= TEXT_PLAIN
;
2484 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
) {
2485 /* This never should fail, but just in case. */
2486 adios (NULL
, "unable to get content info for reply");
2490 /* free_encoding() will close reply_fp, which is passed through
2491 ct->c_cefile.ce_fp. */
2495 failed_to_extract_ct
:
2496 if (*reply_fp
) { fclose (*reply_fp
); }