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. Note that
1729 * RFC 2045 always requires a Content-ID header for message/external-body
1732 if ((contentidsw
|| ct
->c_ctexbody
) && ct
->c_id
) {
1733 np
= mh_xstrdup(ID_FIELD
);
1734 vp
= concat (" ", ct
->c_id
, NULL
);
1735 add_header (ct
, np
, vp
);
1738 * output the Content-Description
1741 np
= mh_xstrdup(DESCR_FIELD
);
1742 vp
= concat (" ", ct
->c_descr
, NULL
);
1743 if (header_encoding
!= CE_8BIT
) {
1744 if (encode_rfc2047(DESCR_FIELD
, &vp
, header_encoding
, NULL
)) {
1745 adios(NULL
, "Unable to encode %s header", DESCR_FIELD
);
1748 add_header (ct
, np
, vp
);
1752 * output the Content-Disposition. If it's NULL but c_dispo_type is
1753 * set, then we need to build it.
1756 np
= mh_xstrdup(DISPO_FIELD
);
1757 vp
= concat (" ", ct
->c_dispo
, NULL
);
1758 add_header (ct
, np
, vp
);
1759 } else if (ct
->c_dispo_type
) {
1760 vp
= concat (" ", ct
->c_dispo_type
, NULL
);
1761 len
= strlen(DISPO_FIELD
) + strlen(vp
) + 1;
1762 np
= output_params(len
, ct
->c_dispo_first
, NULL
, 0);
1766 add_header (ct
, mh_xstrdup(DISPO_FIELD
), vp
);
1771 * If this is the internal content structure for a
1772 * "message/external", then we are done with the
1773 * headers (since it has no body).
1779 * output the Content-MD5
1782 np
= mh_xstrdup(MD5_FIELD
);
1783 vp
= calculate_digest (ct
, (ct
->c_encoding
== CE_QUOTED
) ? 1 : 0);
1784 add_header (ct
, np
, vp
);
1788 * output the Content-Transfer-Encoding
1789 * If using EAI and message body is 7-bit, force 8-bit C-T-E.
1791 if (header_encoding
== CE_8BIT
&& ct
->c_encoding
== CE_7BIT
) {
1792 ct
->c_encoding
= CE_8BIT
;
1795 switch (ct
->c_encoding
) {
1797 /* Nothing to output */
1801 np
= mh_xstrdup(ENCODING_FIELD
);
1802 vp
= concat (" ", "8bit", "\n", NULL
);
1803 add_header (ct
, np
, vp
);
1807 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1808 adios (NULL
, "internal error, invalid encoding");
1810 np
= mh_xstrdup(ENCODING_FIELD
);
1811 vp
= concat (" ", "quoted-printable", "\n", NULL
);
1812 add_header (ct
, np
, vp
);
1816 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1817 adios (NULL
, "internal error, invalid encoding");
1819 np
= mh_xstrdup(ENCODING_FIELD
);
1820 vp
= concat (" ", "base64", "\n", NULL
);
1821 add_header (ct
, np
, vp
);
1825 if (ct
->c_type
== CT_MESSAGE
)
1826 adios (NULL
, "internal error, invalid encoding");
1828 np
= mh_xstrdup(ENCODING_FIELD
);
1829 vp
= concat (" ", "binary", "\n", NULL
);
1830 add_header (ct
, np
, vp
);
1834 adios (NULL
, "unknown transfer encoding in content");
1839 * Additional content specific header processing
1841 switch (ct
->c_type
) {
1844 struct multipart
*m
;
1847 m
= (struct multipart
*) ct
->c_ctparams
;
1848 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1852 build_headers (p
, header_encoding
);
1858 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1861 e
= (struct exbody
*) ct
->c_ctparams
;
1862 build_headers (e
->eb_content
, header_encoding
);
1875 static char nib2b64
[0x40+1] =
1876 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1879 calculate_digest (CT ct
, int asciiP
)
1884 unsigned char digest
[16];
1885 unsigned char outbuf
[25];
1887 CE ce
= &ct
->c_cefile
;
1888 char *infilename
= ce
->ce_file
? ce
->ce_file
: ct
->c_file
;
1892 if ((in
= fopen (infilename
, "r")) == NULL
)
1893 adios (infilename
, "unable to open for reading");
1895 /* Initialize md5 context */
1896 MD5Init (&mdContext
);
1898 /* calculate md5 message digest */
1903 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1906 cp
= bufp
+ gotlen
- 1;
1907 if ((c
= *cp
) == '\n')
1910 MD5Update (&mdContext
, (unsigned char *) bufp
,
1911 (unsigned int) gotlen
);
1914 MD5Update (&mdContext
, (unsigned char *) "\r\n", 2);
1917 char buffer
[BUFSIZ
];
1918 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), in
)) > 0)
1919 MD5Update (&mdContext
, (unsigned char *) buffer
, (unsigned int) cc
);
1922 /* md5 finalization. Write digest and zero md5 context */
1923 MD5Final (digest
, &mdContext
);
1928 /* print debugging info */
1932 fprintf (stderr
, "MD5 digest=");
1933 for (ep
= (dp
= digest
) + sizeof(digest
) / sizeof(digest
[0]);
1935 fprintf (stderr
, "%02x", *dp
& 0xff);
1936 fprintf (stderr
, "\n");
1939 /* encode the digest using base64 */
1940 for (dp
= digest
, op
= (char *) outbuf
,
1941 cc
= sizeof(digest
) / sizeof(digest
[0]);
1942 cc
> 0; cc
-= 3, op
+= 4) {
1946 bits
= (*dp
++ & 0xff) << 16;
1948 bits
|= (*dp
++ & 0xff) << 8;
1950 bits
|= *dp
++ & 0xff;
1953 for (bp
= op
+ 4; bp
> op
; bits
>>= 6)
1954 *--bp
= nib2b64
[bits
& 0x3f];
1962 /* null terminate string */
1965 /* now make copy and return string */
1966 vp
= concat (" ", outbuf
, "\n", NULL
);
1971 * Set things up for the content structure for file "filename" that
1976 setup_attach_content(CT ct
, char *filename
)
1978 char *type
, *simplename
= r1bindex(filename
, '/');
1979 struct str2init
*s2i
;
1982 if (! (type
= mime_type(filename
))) {
1983 adios(NULL
, "Unable to determine MIME type of \"%s\"", filename
);
1987 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1988 * since we're just feeding it a MIME type we have to add those ourself.
1989 * Map that to a valid content-type label and call any initialization
1993 if (get_ctinfo(type
, ct
, 0) == NOTOK
)
1998 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
1999 if (strcasecmp(ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0)
2001 if (!s2i
->si_key
&& !uprf(ct
->c_ctinfo
.ci_type
, "X-"))
2005 * Make sure the type isn't incompatible with what we can handle
2008 switch (ct
->c_type
= s2i
->si_val
) {
2010 adios (NULL
, "multipart types must be specified by mhbuild directives");
2014 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "partial") == 0)
2015 adios(NULL
, "Sorry, %s/%s isn't supported", ct
->c_ctinfo
.ci_type
,
2016 ct
->c_ctinfo
.ci_subtype
);
2017 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "external-body") == 0)
2018 adios(NULL
, "external-body messages must be specified "
2019 "by mhbuild directives");
2024 * This sets the subtype, if it's significant
2026 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
2027 (*ct
->c_ctinitfnx
)(ct
);
2032 * Feed in a few attributes; specifically, the name attribute, the
2033 * content-description, and the content-disposition.
2036 for (pm
= ct
->c_ctinfo
.ci_first_pm
; pm
; pm
= pm
->pm_next
) {
2037 if (strcasecmp(pm
->pm_name
, "name") == 0) {
2038 mh_xfree(pm
->pm_value
);
2039 pm
->pm_value
= mh_xstrdup(simplename
);
2045 add_param(&ct
->c_ctinfo
.ci_first_pm
, &ct
->c_ctinfo
.ci_last_pm
,
2046 "name", simplename
, 0);
2048 ct
->c_descr
= mh_xstrdup(simplename
);
2049 ct
->c_descr
= add("\n", ct
->c_descr
);
2050 ct
->c_cefile
.ce_file
= mh_xstrdup(filename
);
2052 set_disposition (ct
);
2054 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename", simplename
, 0);
2058 * If disposition type hasn't already been set in ct:
2059 * Look for mhbuild-disposition-<type>/<subtype> entry
2060 * that specifies Content-Disposition type. Only
2061 * 'attachment' and 'inline' are allowed. Default to
2065 set_disposition (CT ct
) {
2066 if (ct
->c_dispo_type
== NULL
) {
2067 char *cp
= context_find_by_type ("disposition", ct
->c_ctinfo
.ci_type
,
2068 ct
->c_ctinfo
.ci_subtype
);
2070 if (cp
&& strcasecmp (cp
, "attachment") &&
2071 strcasecmp (cp
, "inline")) {
2072 admonish (NULL
, "configuration problem: %s-disposition-%s%s%s "
2073 "specifies '%s' but only 'attachment' and 'inline' are "
2074 "allowed", invo_name
,
2075 ct
->c_ctinfo
.ci_type
,
2076 ct
->c_ctinfo
.ci_subtype
? "/" : "",
2077 ct
->c_ctinfo
.ci_subtype
? ct
->c_ctinfo
.ci_subtype
: "",
2083 ct
->c_dispo_type
= mh_xstrdup(cp
);
2088 * Set text content charset if it was unspecified. contains8bit
2090 * 0: content does not contain 8-bit characters
2091 * 1: content contains 8-bit characters
2092 * -1: ignore content and use user's locale to determine charset
2095 set_charset (CT ct
, int contains8bit
) {
2096 if (ct
->c_type
== CT_TEXT
) {
2099 if (ct
->c_ctparams
== NULL
) {
2102 t
->tx_charset
= CHARSET_UNSPECIFIED
;
2104 t
= (struct text
*) ct
->c_ctparams
;
2107 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
2108 CI ci
= &ct
->c_ctinfo
;
2109 char *eightbitcharset
= write_charset_8bit();
2110 char *charset
= contains8bit
? eightbitcharset
: "us-ascii";
2112 if (contains8bit
== 1 &&
2113 strcasecmp (eightbitcharset
, "US-ASCII") == 0) {
2114 adios (NULL
, "Text content contains 8 bit characters, but "
2115 "character set is US-ASCII");
2118 add_param (&ci
->ci_first_pm
, &ci
->ci_last_pm
, "charset", charset
,
2121 t
->tx_charset
= CHARSET_SPECIFIED
;
2128 * Look at all of the replied-to message parts and expand any that
2129 * are matched by a pseudoheader. Except don't descend into
2133 expand_pseudoheaders (CT ct
, struct multipart
*m
, const char *infile
,
2134 const convert_list
*convert_head
) {
2135 /* text_plain_ct is used to concatenate all of the text/plain
2136 replies into one part, instead of having each one in a separate
2138 CT text_plain_ct
= NULL
;
2140 switch (ct
->c_type
) {
2141 case CT_MULTIPART
: {
2142 struct multipart
*mp
= (struct multipart
*) ct
->c_ctparams
;
2145 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
2148 /* The parts are in descending priority order (defined by
2149 RFC 2046 Sec. 5.1.4) because they were reversed by
2150 parse_mime (). So, stop looking for matches with
2151 immediate subparts after the first match of an
2153 for (part
= mp
->mp_parts
; ! matched
&& part
; part
= part
->mp_next
) {
2154 char *type_subtype
=
2155 concat (part
->mp_part
->c_ctinfo
.ci_type
, "/",
2156 part
->mp_part
->c_ctinfo
.ci_subtype
, NULL
);
2158 if (part
->mp_part
->c_type
== CT_MULTIPART
) {
2159 expand_pseudoheaders (part
->mp_part
, m
, infile
,
2162 const convert_list
*c
;
2164 for (c
= convert_head
; c
; c
= c
->next
) {
2165 if (! strcasecmp (type_subtype
, c
->type
)) {
2166 expand_pseudoheader (part
->mp_part
, &text_plain_ct
,
2168 c
->type
, c
->argstring
);
2174 free (type_subtype
);
2177 for (part
= mp
->mp_parts
; part
; part
= part
->mp_next
) {
2178 expand_pseudoheaders (part
->mp_part
, m
, infile
, convert_head
);
2185 char *type_subtype
=
2186 concat (ct
->c_ctinfo
.ci_type
, "/", ct
->c_ctinfo
.ci_subtype
,
2188 const convert_list
*c
;
2190 for (c
= convert_head
; c
; c
= c
->next
) {
2191 if (! strcasecmp (type_subtype
, c
->type
)) {
2192 expand_pseudoheader (ct
, &text_plain_ct
, m
, infile
, c
->type
,
2197 free (type_subtype
);
2205 * Expand a single pseudoheader. It's for the specified type.
2208 expand_pseudoheader (CT ct
, CT
*text_plain_ct
, struct multipart
*m
,
2209 const char *infile
, const char *type
,
2210 const char *argstring
) {
2212 FILE *reply_fp
= NULL
;
2213 char *convert
, *type_p
, *subtype_p
;
2214 char *convert_command
;
2215 char *charset
= NULL
;
2217 struct str2init
*s2i
;
2223 type_p
= getcpy (type
);
2224 if ((subtype_p
= strchr (type_p
, '/'))) {
2225 *subtype_p
++ = '\0';
2226 convert
= context_find_by_type ("convert", type_p
, subtype_p
);
2229 type_p
= concat ("mhbuild-convert-", type
, NULL
);
2230 convert
= context_find (type_p
);
2235 /* No mhbuild-convert- entry in mhn.defaults or profile
2239 /* reply_file is used to pass the output of the convert. */
2240 reply_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2242 concat (convert
, " ", argstring
? argstring
: "", " >", reply_file
,
2245 /* Convert here . . . */
2246 ct
->c_storeproc
= mh_xstrdup(convert_command
);
2247 ct
->c_umask
= ~m_gmprot ();
2249 if ((status
= show_content_aux (ct
, 0, convert_command
, NULL
, NULL
)) !=
2251 admonish (NULL
, "store of %s content failed", type
);
2253 free (convert_command
);
2255 /* Fill out the the new ct, reply_ct. */
2257 init_decoded_content (reply_ct
, infile
);
2259 if (extract_headers (reply_ct
, reply_file
, &reply_fp
) == NOTOK
) {
2262 "failed to extract headers from convert output in %s",
2267 /* For text content only, see if it is 8-bit text. */
2268 if (reply_ct
->c_type
== CT_TEXT
) {
2271 if ((fd
= open (reply_file
, O_RDONLY
)) == NOTOK
||
2272 scan_input (fd
, &eightbit
) == NOTOK
) {
2274 admonish (NULL
, "failed to read %s", reply_file
);
2280 /* This sets reply_ct->c_ctparams, and reply_ct->c_termproc if the
2281 charset can't be handled natively. */
2282 for (s2i
= str2cts
; s2i
->si_key
; s2i
++) {
2283 if (strcasecmp(reply_ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0) {
2288 if ((reply_ct
->c_ctinitfnx
= s2i
->si_init
)) {
2289 (*reply_ct
->c_ctinitfnx
)(reply_ct
);
2293 get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1))) {
2294 /* The reply Content-Type had the charset. */
2297 set_charset (reply_ct
, -1);
2298 charset
= get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1);
2299 if (reply_ct
->c_reqencoding
== CE_UNKNOWN
&&
2300 reply_ct
->c_type
== CT_TEXT
) {
2301 /* Assume that 8bit is sufficient (for text). In other words,
2302 don't allow it to be encoded as quoted printable if lines
2303 are too long. This also sidesteps the check for whether
2304 it needs to be encoded as binary; instead, it relies on
2305 the applicable mhbuild-convert-text directive to ensure
2306 that the resultant text is not binary. */
2307 reply_ct
->c_reqencoding
= eightbit
? CE_8BIT
: CE_7BIT
;
2311 /* Concatenate text/plain parts. */
2312 if (reply_ct
->c_type
== CT_TEXT
&&
2313 reply_ct
->c_subtype
== TEXT_PLAIN
) {
2314 if (! *text_plain_ct
&& m
->mp_parts
&& m
->mp_parts
->mp_part
&&
2315 m
->mp_parts
->mp_part
->c_type
== CT_TEXT
&&
2316 m
->mp_parts
->mp_part
->c_subtype
== TEXT_PLAIN
) {
2317 *text_plain_ct
= m
->mp_parts
->mp_part
;
2318 /* Make sure that the charset is set in the text/plain
2320 set_charset (*text_plain_ct
, -1);
2321 if ((*text_plain_ct
)->c_reqencoding
== CE_UNKNOWN
) {
2322 /* Assume that 8bit is sufficient (for text). In other words,
2323 don't allow it to be encoded as quoted printable if lines
2324 are too long. This also sidesteps the check for whether
2325 it needs to be encoded as binary; instead, it relies on
2326 the applicable mhbuild-convert-text directive to ensure
2327 that the resultant text is not binary. */
2328 (*text_plain_ct
)->c_reqencoding
=
2329 eightbit
? CE_8BIT
: CE_7BIT
;
2333 if (*text_plain_ct
) {
2334 /* Only concatenate if the charsets are identical. */
2335 char *text_plain_ct_charset
=
2336 get_param ((*text_plain_ct
)->c_ctinfo
.ci_first_pm
, "charset",
2339 if (strcasecmp (text_plain_ct_charset
, charset
) == 0) {
2340 /* Append this text/plain reply to the first one.
2341 If there's a problem anywhere along the way,
2342 instead attach it is a separate part. */
2343 int text_plain_reply
=
2344 open ((*text_plain_ct
)->c_cefile
.ce_file
,
2345 O_WRONLY
| O_APPEND
);
2346 int addl_reply
= open (reply_file
, O_RDONLY
);
2348 if (text_plain_reply
!= NOTOK
&& addl_reply
!= NOTOK
) {
2349 /* Insert blank line before each addl part. */
2350 /* It would be nice not to do this for the first one. */
2351 if (write (text_plain_reply
, "\n", 1) == 1) {
2352 /* Copy the text from the new reply and
2353 then free its Content struct. */
2354 cpydata (addl_reply
, text_plain_reply
,
2355 (*text_plain_ct
)->c_cefile
.ce_file
,
2357 if (close (text_plain_reply
) == OK
&&
2358 close (addl_reply
) == OK
) {
2359 /* If appended text needed 8-bit but first text didn't,
2360 propagate the 8-bit indication. */
2361 if ((*text_plain_ct
)->c_reqencoding
== CE_7BIT
&&
2362 reply_ct
->c_reqencoding
== CE_8BIT
) {
2363 (*text_plain_ct
)->c_reqencoding
= CE_8BIT
;
2366 if (reply_fp
) { fclose (reply_fp
); }
2368 free_content (reply_ct
);
2375 *text_plain_ct
= reply_ct
;
2379 reply_ct
->c_cefile
.ce_file
= reply_file
;
2380 reply_ct
->c_cefile
.ce_fp
= reply_fp
;
2381 reply_ct
->c_cefile
.ce_unlink
= 1;
2383 /* Attach the new part to the parent multipart/mixed, "m". */
2385 part
->mp_part
= reply_ct
;
2389 for (p
= m
->mp_parts
; p
&& p
->mp_next
; p
= p
->mp_next
) { continue; }
2397 /* Extract any Content-Type header from beginning of convert output. */
2399 extract_headers (CT ct
, char *reply_file
, FILE **reply_fp
) {
2400 char *buffer
= NULL
, *cp
, *end_of_header
;
2401 int found_header
= 0;
2402 struct stat statbuf
;
2404 /* Read the convert reply from the file to memory. */
2405 if (stat (reply_file
, &statbuf
) == NOTOK
) {
2406 admonish (reply_file
, "failed to stat");
2407 goto failed_to_extract_ct
;
2410 buffer
= mh_xmalloc (statbuf
.st_size
+ 1);
2412 if ((*reply_fp
= fopen (reply_file
, "r+")) == NULL
||
2413 fread (buffer
, 1, (size_t) statbuf
.st_size
, *reply_fp
) <
2414 (size_t) statbuf
.st_size
) {
2415 admonish (reply_file
, "failed to read");
2416 goto failed_to_extract_ct
;
2418 buffer
[statbuf
.st_size
] = '\0';
2420 /* Look for a header in the convert reply. */
2421 if (strncasecmp (buffer
, TYPE_FIELD
, strlen (TYPE_FIELD
)) == 0 &&
2422 buffer
[strlen (TYPE_FIELD
)] == ':') {
2423 if ((end_of_header
= strstr (buffer
, "\r\n\r\n"))) {
2426 } else if ((end_of_header
= strstr (buffer
, "\n\n"))) {
2438 /* Truncate buffer to just the C-T. */
2439 *end_of_header
= '\0';
2440 n
= strlen (buffer
);
2442 if (get_ctinfo (buffer
+ 14, ct
, 0) != OK
) {
2443 admonish (NULL
, "unable to get content info for reply");
2444 goto failed_to_extract_ct
;
2447 /* Hack. Use parse_mime() to detect the type/subtype of the
2448 reply, which we'll use below. */
2449 tmp_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2450 if ((tmp_f
= fopen (tmp_file
, "w")) &&
2451 fwrite (buffer
, 1, n
, tmp_f
) == n
) {
2454 goto failed_to_extract_ct
;
2456 tmp_ct
= parse_mime (tmp_file
);
2459 /* The type and subtype were detected from the reply
2460 using parse_mime() above. */
2461 ct
->c_type
= tmp_ct
->c_type
;
2462 ct
->c_subtype
= tmp_ct
->c_subtype
;
2463 free_content (tmp_ct
);
2468 /* Rewrite the content without the header. */
2469 cp
= end_of_header
+ 1;
2472 if (fwrite (cp
, 1, statbuf
.st_size
- (cp
- buffer
), *reply_fp
) <
2473 (size_t) (statbuf
.st_size
- (cp
- buffer
))) {
2474 admonish (reply_file
, "failed to write");
2475 goto failed_to_extract_ct
;
2478 if (ftruncate (fileno (*reply_fp
), statbuf
.st_size
- (cp
- buffer
)) !=
2480 advise (reply_file
, "ftruncate");
2481 goto failed_to_extract_ct
;
2484 /* No header section, assume the reply is text/plain. */
2485 ct
->c_type
= CT_TEXT
;
2486 ct
->c_subtype
= TEXT_PLAIN
;
2487 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
) {
2488 /* This never should fail, but just in case. */
2489 adios (NULL
, "unable to get content info for reply");
2493 /* free_encoding() will close reply_fp, which is passed through
2494 ct->c_cefile.ce_fp. */
2498 failed_to_extract_ct
:
2499 if (*reply_fp
) { fclose (*reply_fp
); }