2 * mhbuildsbr.c -- routines to expand/translate MIME composition files
4 * This code is Copyright (c) 2002, by the authors of nmh. See the
5 * COPYRIGHT file in the root directory of the nmh distribution for
6 * complete copyright information.
10 * This code was originally part of mhn.c. I split it into
11 * a separate program (mhbuild.c) and then later split it
12 * again (mhbuildsbr.c). But the code still has some of
13 * the mhn.c code in it. This program needs additional
14 * streamlining and removal of unneeded code.
23 #include <h/mhparse.h>
26 #ifdef HAVE_SYS_TIME_H
27 # include <sys/time.h>
36 extern int contentidsw
;
39 extern int rcachesw
; /* mhcachesbr.c */
40 extern int wcachesw
; /* mhcachesbr.c */
42 static char prefix
[] = "----- =_aaaaaaaaaa";
46 struct attach_list
*next
;
49 typedef struct convert_list
{
53 struct convert_list
*next
;
58 void content_error (char *, CT
, char *, ...);
61 int find_cache (CT
, int, int *, char *, char *, int);
65 void freects_done (int) NORETURN
;
66 void free_ctinfo (CT
);
67 void free_encoding (CT
, int);
72 static int init_decoded_content (CT
, const char *);
73 static void setup_attach_content(CT
, char *);
74 static void set_disposition (CT
);
75 static void set_charset (CT
, int);
76 static void expand_pseudoheaders (CT
, struct multipart
*, const char *,
77 const convert_list
*);
78 static void expand_pseudoheader (CT
, CT
*, struct multipart
*, const char *,
79 const char *, const char *);
80 static char *fgetstr (char *, int, FILE *);
81 static int user_content (FILE *, char *, CT
*, const char *infilename
);
82 static void set_id (CT
, int);
83 static int compose_content (CT
, int);
84 static int scan_content (CT
, size_t);
85 static int build_headers (CT
, int);
86 static char *calculate_digest (CT
, int);
87 static int extract_headers (CT
, char *, FILE **);
90 static unsigned char directives_stack
[32];
91 static unsigned int directives_index
;
93 static int do_direct(void)
95 return directives_stack
[directives_index
];
98 static void directive_onoff(int onoff
)
100 if (directives_index
>= sizeof(directives_stack
) - 1) {
101 fprintf(stderr
, "mhbuild: #on/off overflow, continuing\n");
104 directives_stack
[++directives_index
] = onoff
;
107 static void directive_init(int onoff
)
109 directives_index
= 0;
110 directives_stack
[0] = onoff
;
113 static void directive_pop(void)
115 if (directives_index
> 0)
118 fprintf(stderr
, "mhbuild: #pop underflow, continuing\n");
122 * Main routine for translating composition file
123 * into valid MIME message. It translates the draft
124 * into a content structure (actually a tree of content
125 * structures). This message then can be manipulated
126 * in various ways, including being output via
131 build_mime (char *infile
, int autobuild
, int dist
, int directives
,
132 int header_encoding
, size_t maxunencoded
, int verbose
)
135 char buf
[BUFSIZ
], name
[NAMESZ
];
142 m_getfld_state_t gstate
= 0;
143 struct attach_list
*attach_head
= NULL
, *attach_tail
= NULL
, *at_entry
;
144 convert_list
*convert_head
= NULL
, *convert_tail
= NULL
, *convert
;
146 directive_init(directives
);
148 umask (~m_gmprot ());
150 /* open the composition draft */
151 if ((in
= fopen (infile
, "r")) == NULL
)
152 adios (infile
, "unable to open for reading");
155 * Allocate space for primary (outside) content
160 * Allocate structure for handling decoded content
161 * for this part. We don't really need this, but
162 * allocate it to remain consistent.
164 init_decoded_content (ct
, infile
);
167 * Parse some of the header fields in the composition
168 * draft into the linked list of header fields for
169 * the new MIME message.
171 m_getfld_track_filepos (&gstate
, in
);
172 for (compnum
= 1;;) {
173 int bufsz
= sizeof buf
;
174 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
179 /* abort if draft has Mime-Version or C-T-E header field */
180 if (strcasecmp (name
, VRSN_FIELD
) == 0 ||
181 strcasecmp (name
, ENCODING_FIELD
) == 0) {
187 adios (NULL
, "draft shouldn't contain %s: field", name
);
190 /* ignore any Content-Type fields in the header */
191 if (!strcasecmp (name
, TYPE_FIELD
)) {
192 while (state
== FLDPLUS
) {
194 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
199 /* get copies of the buffers */
200 np
= mh_xstrdup(name
);
201 vp
= mh_xstrdup(buf
);
203 /* if necessary, get rest of field */
204 while (state
== FLDPLUS
) {
206 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
207 vp
= add (buf
, vp
); /* add to previous value */
211 * Now add the header data to the list, unless it's an attach
212 * header; in that case, add it to our attach list
215 if (strcasecmp(ATTACH_FIELD
, np
) == 0 ||
216 strcasecmp(ATTACH_FIELD_ALT
, np
) == 0) {
217 struct attach_list
*entry
;
218 char *s
= vp
, *e
= vp
+ strlen(vp
) - 1;
222 * Make sure we can find the start of this filename.
223 * If it's blank, we skip completely. Otherwise, strip
224 * off any leading spaces and trailing newlines.
227 while (isspace((unsigned char) *s
))
230 while (e
> s
&& *e
== '\n')
239 entry
->filename
= mh_xstrdup(s
);
244 attach_tail
->next
= entry
;
247 attach_head
= attach_tail
= entry
;
249 } else if (strncasecmp(MHBUILD_FILE_PSEUDOHEADER
, np
,
250 strlen (MHBUILD_FILE_PSEUDOHEADER
)) == 0) {
252 * Nmh-mhbuild-file-text/calendar: /home/user/Mail/inbox/9
254 char *type
= np
+ strlen (MHBUILD_FILE_PSEUDOHEADER
);
257 /* vp should begin with a space because m_getfld()
258 includes the space after the colon in buf. */
259 while (isspace((unsigned char) *filename
)) { ++filename
; }
260 /* Trim trailing newline and any other whitespace. */
263 for (convert
= convert_head
; convert
; convert
= convert
->next
) {
264 if (strcasecmp (convert
->type
, type
) == 0) { break; }
267 if (convert
->filename
&&
268 strcasecmp (convert
->filename
, filename
)) {
269 adios (NULL
, "Multiple %s headers with different files"
270 " not allowed", type
);
272 convert
->filename
= mh_xstrdup(filename
);
276 convert
->filename
= mh_xstrdup(filename
);
277 convert
->type
= mh_xstrdup(type
);
280 convert_tail
->next
= convert
;
282 convert_head
= convert
;
284 convert_tail
= convert
;
289 } else if (strncasecmp(MHBUILD_ARGS_PSEUDOHEADER
, np
,
290 strlen (MHBUILD_ARGS_PSEUDOHEADER
)) == 0) {
292 * Nmh-mhbuild-args-text/calendar: -reply accept
294 char *type
= np
+ strlen (MHBUILD_ARGS_PSEUDOHEADER
);
295 char *argstring
= vp
;
297 /* vp should begin with a space because m_getfld()
298 includes the space after the colon in buf. */
299 while (isspace((unsigned char) *argstring
)) { ++argstring
; }
300 /* Trim trailing newline and any other whitespace. */
303 for (convert
= convert_head
; convert
; convert
= convert
->next
) {
304 if (strcasecmp (convert
->type
, type
) == 0) { break; }
307 if (convert
->argstring
&&
308 strcasecmp (convert
->argstring
, argstring
)) {
309 adios (NULL
, "Multiple %s headers with different "
310 "argstrings not allowed", type
);
312 convert
->argstring
= mh_xstrdup(argstring
);
316 convert
->type
= mh_xstrdup(type
);
317 convert
->argstring
= mh_xstrdup(argstring
);
320 convert_tail
->next
= convert
;
322 convert_head
= convert
;
324 convert_tail
= convert
;
330 add_header (ct
, np
, vp
);
334 /* if this wasn't the last header field, then continue */
338 fseek (in
, (long) (-strlen (buf
)), SEEK_CUR
);
345 adios (NULL
, "message format error in component #%d", compnum
);
348 adios (NULL
, "getfld() returned %d", state
);
352 m_getfld_state_destroy (&gstate
);
354 if (header_encoding
!= CE_8BIT
) {
356 * Iterate through the list of headers and call the function to MIME-ify
360 for (hp
= ct
->c_first_hf
; hp
!= NULL
; hp
= hp
->next
) {
361 if (encode_rfc2047(hp
->name
, &hp
->value
, header_encoding
, NULL
)) {
362 adios(NULL
, "Unable to encode header \"%s\"", hp
->name
);
368 * Now add the MIME-Version header field
369 * to the list of header fields.
373 np
= mh_xstrdup(VRSN_FIELD
);
374 vp
= concat (" ", VRSN_VALUE
, "\n", NULL
);
375 add_header (ct
, np
, vp
);
379 * We initally assume we will find multiple contents in the
380 * draft. So create a multipart/mixed content to hold everything.
381 * We can remove this later, if it is not needed.
383 if (get_ctinfo ("multipart/mixed", ct
, 0) == NOTOK
)
385 ct
->c_type
= CT_MULTIPART
;
386 ct
->c_subtype
= MULTI_MIXED
;
389 ct
->c_ctparams
= (void *) m
;
393 * read and parse the composition file
394 * and the directives it contains.
396 while (fgetstr (buf
, sizeof(buf
) - 1, in
)) {
400 if (user_content (in
, buf
, &p
, infile
) == DONE
) {
401 inform("ignoring spurious #end, continuing...");
414 * Add any Attach headers to the list of MIME parts at the end of the
418 for (at_entry
= attach_head
; at_entry
; ) {
419 struct attach_list
*at_prev
= at_entry
;
423 if (access(at_entry
->filename
, R_OK
) != 0) {
424 adios("reading", "Unable to open %s for", at_entry
->filename
);
428 init_decoded_content(p
, infile
);
431 * Initialize our content structure based on the filename,
432 * and fill in all of the relevant fields. Also place MIME
433 * parameters in the attributes array.
436 setup_attach_content(p
, at_entry
->filename
);
443 at_entry
= at_entry
->next
;
444 free(at_prev
->filename
);
449 * Handle the mhbuild pseudoheaders, which deal with specific
458 /* In case there are multiple calls that land here, prevent leak. */
459 for (ctp
= cts
; ctp
&& *ctp
; ++ctp
) { free_content (*ctp
); }
462 /* Extract the type part (as a CT) from filename. */
463 cts
= mh_xcalloc(2, sizeof *cts
);
464 if (! (cts
[0] = parse_mime (convert_head
->filename
))) {
465 adios (NULL
, "failed to parse %s", convert_head
->filename
);
468 expand_pseudoheaders (cts
[0], m
, infile
, convert_head
);
470 /* Free the convert list. */
471 for (convert
= convert_head
; convert
; convert
= next
) {
472 next
= convert
->next
;
473 free (convert
->type
);
474 free (convert
->filename
);
475 free (convert
->argstring
);
482 * To allow for empty message bodies, if we've found NO content at all
483 * yet cook up an empty text/plain part.
492 init_decoded_content(p
, infile
);
494 if (get_ctinfo ("text/plain", p
, 0) == NOTOK
)
498 p
->c_subtype
= TEXT_PLAIN
;
499 p
->c_encoding
= CE_7BIT
;
501 * Sigh. ce_file contains the "decoded" contents of this part.
502 * So this seems like the best option available since we're going
503 * to call scan_content() on this.
505 p
->c_cefile
.ce_file
= mh_xstrdup("/dev/null");
506 p
->c_begin
= ftell(in
);
507 p
->c_end
= ftell(in
);
510 t
->tx_charset
= CHARSET_SPECIFIED
;
519 * close the composition draft since
520 * it's not needed any longer.
525 * If only one content was found, then remove and
526 * free the outer multipart content.
528 if (!m
->mp_parts
->mp_next
) {
531 p
= m
->mp_parts
->mp_part
;
532 m
->mp_parts
->mp_part
= NULL
;
534 /* move header fields */
535 p
->c_first_hf
= ct
->c_first_hf
;
536 p
->c_last_hf
= ct
->c_last_hf
;
537 ct
->c_first_hf
= NULL
;
538 ct
->c_last_hf
= NULL
;
547 * Fill out, or expand directives. Parse and execute
548 * commands specified by profile composition strings.
550 compose_content (ct
, verbose
);
552 if ((cp
= strchr(prefix
, 'a')) == NULL
)
553 adios (NULL
, "internal error(4)");
556 * If using EAI, force 8-bit charset.
558 if (header_encoding
== CE_8BIT
) {
563 * Scan the contents. Choose a transfer encoding, and
564 * check if prefix for multipart boundary clashes with
565 * any of the contents.
567 while (scan_content (ct
, maxunencoded
) == NOTOK
) {
572 adios (NULL
, "giving up trying to find a unique delimiter string");
578 /* Build the rest of the header field structures */
580 build_headers (ct
, header_encoding
);
587 * Set up structures for placing unencoded
588 * content when building parts.
592 init_decoded_content (CT ct
, const char *filename
)
594 ct
->c_ceopenfnx
= open7Bit
; /* since unencoded */
595 ct
->c_ceclosefnx
= close_encoding
;
596 ct
->c_cesizefnx
= NULL
; /* since unencoded */
597 ct
->c_encoding
= CE_7BIT
; /* Seems like a reasonable default */
598 ct
->c_file
= add(filename
, NULL
);
605 fgetstr (char *s
, int n
, FILE *stream
)
611 for (cp
= s
; cp
< ep
;) {
614 if (!fgets (cp
, n
, stream
))
615 return cp
== s
? NULL
: s
; /* "\\\nEOF" ignored. */
617 if (! do_direct() || (cp
== s
&& *cp
!= '#'))
618 return s
; /* Plaintext line. */
622 break; /* Can't contain "\\\n". */
623 cp
+= len
- 1; /* Just before NUL. */
624 if (*cp
-- != '\n' || *cp
!= '\\')
626 *cp
= '\0'; /* Erase the trailing "\\\n". */
630 if (strcmp(s
, "#on\n") == 0) {
632 } else if (strcmp(s
, "#off\n") == 0) {
634 } else if (strcmp(s
, "#pop\n") == 0) {
644 * Parse the composition draft for text and directives.
645 * Do initial setup of Content structure.
649 user_content (FILE *in
, char *buf
, CT
*ctp
, const char *infilename
)
657 struct str2init
*s2i
;
662 if (buf
[0] == '\n' || (do_direct() && strcmp (buf
, "#\n") == 0)) {
667 /* allocate basic Content structure */
671 /* allocate basic structure for handling decoded content */
672 init_decoded_content (ct
, infilename
);
679 * Handle inline text. Check if line
680 * is one of the following forms:
682 * 1) doesn't begin with '#' (implicit directive)
683 * 2) begins with "##" (implicit directive)
684 * 3) begins with "#<"
686 if (!do_direct() || buf
[0] != '#' || buf
[1] == '#' || buf
[1] == '<') {
690 char content
[BUFSIZ
];
694 if ((cp
= m_mktemp2(NULL
, invo_name
, NULL
, &out
)) == NULL
) {
695 adios("mhbuildsbr", "unable to create temporary file in %s",
699 /* use a temp file to collect the plain text lines */
700 ce
->ce_file
= mh_xstrdup(cp
);
703 if (do_direct() && (buf
[0] == '#' && buf
[1] == '<')) {
704 strncpy (content
, buf
+ 2, sizeof(content
));
710 /* the directive is implicit */
711 strncpy (content
, "text/plain", sizeof(content
));
713 strncpy (buffer
, (!do_direct() || buf
[0] != '#') ? buf
: buf
+ 1, sizeof(buffer
));
717 if (headers
>= 0 && do_direct() && uprf (buffer
, DESCR_FIELD
)
718 && buffer
[i
= strlen (DESCR_FIELD
)] == ':') {
722 ct
->c_descr
= add (buffer
+ i
+ 1, ct
->c_descr
);
723 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
724 adios (NULL
, "end-of-file after %s: field in plaintext", DESCR_FIELD
);
732 adios (NULL
, "#-directive after %s: field in plaintext", DESCR_FIELD
);
740 if (headers
>= 0 && do_direct() && uprf (buffer
, DISPO_FIELD
)
741 && buffer
[i
= strlen (DISPO_FIELD
)] == ':') {
745 ct
->c_dispo
= add (buffer
+ i
+ 1, ct
->c_dispo
);
746 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
747 adios (NULL
, "end-of-file after %s: field in plaintext", DISPO_FIELD
);
755 adios (NULL
, "#-directive after %s: field in plaintext", DISPO_FIELD
);
763 if (headers
!= 1 || buffer
[0] != '\n')
769 if ((cp
= fgetstr (buffer
, sizeof(buffer
) - 1, in
)) == NULL
)
771 if (do_direct() && buffer
[0] == '#') {
774 if (buffer
[1] != '#')
776 for (cp
= (bp
= buffer
) + 1; *cp
; cp
++)
783 ct
->c_end
= ftell (out
);
786 /* parse content type */
787 if (get_ctinfo (content
, ct
, inlineD
) == NOTOK
)
790 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
791 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
793 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
797 * check type specified (possibly implicitly)
799 switch (ct
->c_type
= s2i
->si_val
) {
801 if (!strcasecmp (ci
->ci_subtype
, "rfc822")) {
802 ct
->c_encoding
= CE_7BIT
;
807 adios (NULL
, "it doesn't make sense to define an in-line %s content",
808 ct
->c_type
== CT_MESSAGE
? "message" : "multipart");
813 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
814 (*ct
->c_ctinitfnx
) (ct
);
819 fseek (in
, pos
, SEEK_SET
);
824 * If we've reached this point, the next line
825 * must be some type of explicit directive.
828 /* check if directive is external-type */
829 extrnal
= (buf
[1] == '@');
831 /* parse directive */
832 if (get_ctinfo (buf
+ (extrnal
? 2 : 1), ct
, 1) == NOTOK
)
835 /* check directive against the list of MIME types */
836 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
837 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
841 * Check if the directive specified a valid type.
842 * This will happen if it was one of the following forms:
849 adios (NULL
, "missing subtype in \"#%s\"", ci
->ci_type
);
851 switch (ct
->c_type
= s2i
->si_val
) {
853 adios (NULL
, "use \"#begin ... #end\" instead of \"#%s/%s\"",
854 ci
->ci_type
, ci
->ci_subtype
);
858 if (!strcasecmp (ci
->ci_subtype
, "partial"))
859 adios (NULL
, "sorry, \"#%s/%s\" isn't supported",
860 ci
->ci_type
, ci
->ci_subtype
);
861 if (!strcasecmp (ci
->ci_subtype
, "external-body"))
862 adios (NULL
, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
863 ci
->ci_type
, ci
->ci_subtype
);
866 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
867 ci
->ci_type
, ci
->ci_subtype
);
871 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
872 (*ct
->c_ctinitfnx
) (ct
);
877 * #@type/subtype (external types directive)
884 adios (NULL
, "need external information for \"#@%s/%s\"",
885 ci
->ci_type
, ci
->ci_subtype
);
888 snprintf (buffer
, sizeof(buffer
), "message/external-body; %s", ci
->ci_magic
);
893 * Since we are using the current Content structure to
894 * hold information about the type of the external
895 * reference, we need to create another Content structure
896 * for the message/external-body to wrap it in.
899 init_decoded_content(ct
, infilename
);
901 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
903 ct
->c_type
= CT_MESSAGE
;
904 ct
->c_subtype
= MESSAGE_EXTERNAL
;
907 ct
->c_ctparams
= (void *) e
;
913 if (params_external (ct
, 1) == NOTOK
)
919 /* Handle [file] argument */
921 /* check if specifies command to execute */
922 if (*ci
->ci_magic
== '|' || *ci
->ci_magic
== '!') {
923 for (cp
= ci
->ci_magic
+ 1; isspace ((unsigned char) *cp
); cp
++)
926 adios (NULL
, "empty pipe command for #%s directive", ci
->ci_type
);
931 /* record filename of decoded contents */
932 ce
->ce_file
= ci
->ci_magic
;
933 if (access (ce
->ce_file
, R_OK
) == NOTOK
)
934 adios ("reading", "unable to access %s for", ce
->ce_file
);
935 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
936 ct
->c_end
= (long) st
.st_size
;
943 * No [file] argument, so check profile for
944 * method to compose content.
946 cp
= context_find_by_type ("compose", ci
->ci_type
, ci
->ci_subtype
);
948 content_error (NULL
, ct
, "don't know how to compose content");
951 ci
->ci_magic
= mh_xstrdup(cp
);
956 adios (NULL
, "external definition not allowed for \"#%s\"", ci
->ci_type
);
960 * #forw [+folder] [msgs]
962 if (!strcasecmp (ci
->ci_type
, "forw")) {
964 char *folder
, *arguments
[MAXARGS
];
968 ap
= brkstring (ci
->ci_magic
, " ", "\n");
969 copyip (ap
, arguments
, MAXARGS
);
971 arguments
[0] = "cur";
976 /* search the arguments for a folder name */
977 for (ap
= arguments
; *ap
; ap
++) {
979 if (*cp
== '+' || *cp
== '@') {
981 adios (NULL
, "only one folder per #forw directive");
983 folder
= pluspath (cp
);
987 /* else, use the current folder */
989 folder
= mh_xstrdup(getfolder(1));
991 if (!(mp
= folder_read (folder
, 0)))
992 adios (NULL
, "unable to read folder %s", folder
);
993 for (ap
= arguments
; *ap
; ap
++) {
995 if (*cp
!= '+' && *cp
!= '@')
996 if (!m_convert (mp
, cp
))
1003 * If there is more than one message to include, make this
1004 * a content of type "multipart/digest" and insert each message
1005 * as a subpart. If there is only one message, then make this
1006 * a content of type "message/rfc822".
1008 if (mp
->numsel
> 1) {
1009 /* we are forwarding multiple messages */
1010 if (get_ctinfo ("multipart/digest", ct
, 0) == NOTOK
)
1012 ct
->c_type
= CT_MULTIPART
;
1013 ct
->c_subtype
= MULTI_DIGEST
;
1016 ct
->c_ctparams
= (void *) m
;
1019 for (msgnum
= mp
->lowsel
; msgnum
<= mp
->hghsel
; msgnum
++) {
1020 if (is_selected(mp
, msgnum
)) {
1026 init_decoded_content (p
, infilename
);
1028 if (get_ctinfo ("message/rfc822", p
, 0) == NOTOK
)
1030 p
->c_type
= CT_MESSAGE
;
1031 p
->c_subtype
= MESSAGE_RFC822
;
1033 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1034 pe
->ce_file
= mh_xstrdup(buffer
);
1035 if (listsw
&& stat (pe
->ce_file
, &st
) != NOTOK
)
1036 p
->c_end
= (long) st
.st_size
;
1040 pp
= &part
->mp_next
;
1045 /* we are forwarding one message */
1046 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
1048 ct
->c_type
= CT_MESSAGE
;
1049 ct
->c_subtype
= MESSAGE_RFC822
;
1051 msgnum
= mp
->lowsel
;
1052 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1053 ce
->ce_file
= mh_xstrdup(buffer
);
1054 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
1055 ct
->c_end
= (long) st
.st_size
;
1058 folder_free (mp
); /* free folder/message structure */
1065 if (!strcasecmp (ci
->ci_type
, "end")) {
1072 * #begin [ alternative | parallel ]
1074 if (!strcasecmp (ci
->ci_type
, "begin")) {
1075 if (!ci
->ci_magic
) {
1077 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1078 } else if (!strcasecmp (ci
->ci_magic
, "alternative")) {
1079 vrsn
= MULTI_ALTERNATE
;
1080 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1081 } else if (!strcasecmp (ci
->ci_magic
, "parallel")) {
1082 vrsn
= MULTI_PARALLEL
;
1083 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1084 } else if (uprf (ci
->ci_magic
, "digest")) {
1087 vrsn
= MULTI_UNKNOWN
;
1092 snprintf (buffer
, sizeof(buffer
), "multipart/%s", cp
);
1093 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
1095 ct
->c_type
= CT_MULTIPART
;
1096 ct
->c_subtype
= vrsn
;
1099 ct
->c_ctparams
= (void *) m
;
1102 while (fgetstr (buffer
, sizeof(buffer
) - 1, in
)) {
1106 if (user_content (in
, buffer
, &p
, infilename
) == DONE
) {
1108 adios (NULL
, "empty \"#begin ... #end\" sequence");
1116 pp
= &part
->mp_next
;
1119 inform("premature end-of-file, missing #end, continuing...");
1126 adios (NULL
, "unknown directive \"#%s\"", ci
->ci_type
);
1127 return NOTOK
; /* NOT REACHED */
1132 set_id (CT ct
, int top
)
1134 char contentid
[BUFSIZ
];
1136 static time_t clock
= 0;
1137 static char *msgfmt
;
1141 snprintf (contentid
, sizeof(contentid
), "%s\n", message_id (clock
, 1));
1143 msgfmt
= mh_xstrdup(contentid
);
1145 snprintf (contentid
, sizeof(contentid
), msgfmt
, top
? 0 : ++partno
);
1146 ct
->c_id
= mh_xstrdup(contentid
);
1151 * Fill out, or expand the various contents in the composition
1152 * draft. Read-in any necessary files. Parse and execute any
1153 * commands specified by profile composition strings.
1157 compose_content (CT ct
, int verbose
)
1159 CE ce
= &ct
->c_cefile
;
1161 switch (ct
->c_type
) {
1166 char partnam
[BUFSIZ
];
1167 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1171 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1172 pp
= partnam
+ strlen (partnam
);
1177 /* first, we call compose_content on all the subparts */
1178 for (part
= m
->mp_parts
, partnum
= 1; part
; part
= part
->mp_next
, partnum
++) {
1179 CT p
= part
->mp_part
;
1181 sprintf (pp
, "%d", partnum
);
1182 p
->c_partno
= mh_xstrdup(partnam
);
1183 if (compose_content (p
, verbose
) == NOTOK
)
1188 * If the -rfc934mode switch is given, then check all
1189 * the subparts of a multipart/digest. If they are all
1190 * message/rfc822, then mark this content and all
1191 * subparts with the rfc934 compatibility mode flag.
1193 if (rfc934sw
&& ct
->c_subtype
== MULTI_DIGEST
) {
1196 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1197 CT p
= part
->mp_part
;
1199 if (p
->c_subtype
!= MESSAGE_RFC822
) {
1204 ct
->c_rfc934
= is934
;
1205 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1206 CT p
= part
->mp_part
;
1208 if ((p
->c_rfc934
= is934
))
1214 ct
->c_end
= (partnum
= strlen (prefix
) + 2) + 2;
1218 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
)
1219 ct
->c_end
+= part
->mp_part
->c_end
+ partnum
;
1225 /* Nothing to do for type message */
1229 * Discrete types (text/application/audio/image/video)
1234 int i
, xstdout
, len
, buflen
;
1236 char *vec
[4], buffer
[BUFSIZ
];
1238 CI ci
= &ct
->c_ctinfo
;
1241 if (!(cp
= ci
->ci_magic
))
1242 adios (NULL
, "internal error(5)");
1244 if ((tfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1245 adios("mhbuildsbr", "unable to create temporary file in %s",
1248 ce
->ce_file
= mh_xstrdup(tfile
);
1253 /* Get buffer ready to go */
1256 buflen
= sizeof(buffer
);
1259 * Parse composition string into buffer
1261 for ( ; *cp
; cp
++) {
1266 /* insert parameters from directive */
1270 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1271 snprintf (bp
, buflen
, "%s%s=\"%s\"", s
,
1272 pm
->pm_name
, get_param_value(pm
, '?'));
1282 /* %f, and stdout is not-redirected */
1288 * insert temporary filename where
1289 * content should be written
1291 snprintf (bp
, buflen
, "%s", ce
->ce_file
);
1295 /* insert content subtype */
1296 strncpy (bp
, ci
->ci_subtype
, buflen
);
1300 /* insert character % */
1321 printf ("composing content %s/%s from command\n\t%s\n",
1322 ci
->ci_type
, ci
->ci_subtype
, buffer
);
1324 fflush (stdout
); /* not sure if need for -noverbose */
1331 if ((out
= fopen (ce
->ce_file
, "w")) == NULL
)
1332 adios (ce
->ce_file
, "unable to open for writing");
1334 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
> 5; i
++)
1338 adios ("fork", "unable to fork");
1343 dup2 (fileno (out
), 1);
1344 close (fileno (out
));
1345 execvp ("/bin/sh", vec
);
1346 fprintf (stderr
, "unable to exec ");
1353 if (pidXwait(child_id
, NULL
))
1359 /* Check size of file */
1360 if (listsw
&& ct
->c_end
== 0L) {
1363 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1364 ct
->c_end
= (long) st
.st_size
;
1376 * 1) choose a transfer encoding.
1377 * 2) check for clashes with multipart boundary string.
1378 * 3) for text content, figure out which character set is being used.
1380 * If there is a clash with one of the contents and the multipart boundary,
1381 * this function will exit with NOTOK. This will cause the scanning process
1382 * to be repeated with a different multipart boundary. It is possible
1383 * (although highly unlikely) that this scan will be repeated multiple times.
1387 scan_content (CT ct
, size_t maxunencoded
)
1390 int check8bit
= 0, contains8bit
= 0; /* check if contains 8bit data */
1391 int checknul
= 0, containsnul
= 0; /* check if contains NULs */
1392 int checklinelen
= 0, linelen
= 0; /* check for long lines */
1393 int checkllinelen
= 0; /* check for extra-long lines */
1394 int checkboundary
= 0, boundaryclash
= 0; /* check if clashes with multipart boundary */
1395 int checklinespace
= 0, linespace
= 0; /* check if any line ends with space */
1400 struct text
*t
= NULL
;
1402 CE ce
= &ct
->c_cefile
;
1405 * handle multipart by scanning all subparts
1406 * and then checking their encoding.
1408 if (ct
->c_type
== CT_MULTIPART
) {
1409 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1412 /* initially mark the domain of enclosing multipart as 7bit */
1413 ct
->c_encoding
= CE_7BIT
;
1415 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1416 CT p
= part
->mp_part
;
1418 if (scan_content (p
, maxunencoded
) == NOTOK
) /* choose encoding for subpart */
1421 /* if necessary, enlarge encoding for enclosing multipart */
1422 if (p
->c_encoding
== CE_BINARY
)
1423 ct
->c_encoding
= CE_BINARY
;
1424 if (p
->c_encoding
== CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
)
1425 ct
->c_encoding
= CE_8BIT
;
1432 * Decide what to check while scanning this content. Note that
1433 * for text content we always check for 8bit characters if the
1434 * charset is unspecified, because that controls whether or not the
1435 * character set is us-ascii or retrieved from the locale. And
1436 * we check even if the charset is specified, to allow setting
1437 * the proper Content-Transfer-Encoding.
1440 if (ct
->c_type
== CT_TEXT
) {
1441 t
= (struct text
*) ct
->c_ctparams
;
1442 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
1448 switch (ct
->c_reqencoding
) {
1459 /* Use the default rules based on content-type */
1460 switch (ct
->c_type
) {
1464 if (ct
->c_subtype
== TEXT_PLAIN
) {
1471 case CT_APPLICATION
:
1483 /* don't check anything for message/external */
1484 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1497 * Don't check anything for these types,
1498 * since we are forcing use of base64, unless
1499 * the content-type was specified by a mhbuild directive.
1510 * Scan the unencoded content
1512 if (check8bit
|| checklinelen
|| checklinespace
|| checkboundary
||
1513 checkllinelen
|| checknul
) {
1514 if ((in
= fopen (ce
->ce_file
, "r")) == NULL
)
1515 adios (ce
->ce_file
, "unable to open for reading");
1516 prefix_len
= strlen (prefix
);
1518 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1520 * Check for 8bit and NUL data.
1522 for (cp
= bufp
; (check8bit
|| checknul
) &&
1523 cp
< bufp
+ gotlen
; cp
++) {
1524 if (!isascii ((unsigned char) *cp
)) {
1526 check8bit
= 0; /* no need to keep checking */
1530 checknul
= 0; /* no need to keep checking */
1535 * Check line length.
1537 if (checklinelen
&& ((size_t)gotlen
> maxunencoded
+ 1)) {
1539 checklinelen
= 0; /* no need to keep checking */
1543 * RFC 5322 specifies that a message cannot contain a line
1544 * greater than 998 characters (excluding the CRLF). If we
1545 * get one of those lines and linelen is NOT set, then abort.
1548 if (checkllinelen
&& !linelen
&&
1549 (gotlen
> MAXLONGLINE
+ 1)) {
1550 adios(NULL
, "Line in content exceeds maximum line limit (%d)",
1555 * Check if line ends with a space.
1557 if (checklinespace
&& (cp
= bufp
+ gotlen
- 2) > bufp
&&
1558 isspace ((unsigned char) *cp
)) {
1560 checklinespace
= 0; /* no need to keep checking */
1564 * Check if content contains a line that clashes
1565 * with our standard boundary for multipart messages.
1567 if (checkboundary
&& bufp
[0] == '-' && bufp
[1] == '-') {
1568 for (cp
= bufp
+ gotlen
- 1; cp
>= bufp
; cp
--)
1569 if (!isspace ((unsigned char) *cp
))
1572 if (!strncmp(bufp
+ 2, prefix
, prefix_len
) &&
1573 isdigit((unsigned char) bufp
[2 + prefix_len
])) {
1575 checkboundary
= 0; /* no need to keep checking */
1584 * If the content is text and didn't specify a character set,
1585 * we need to figure out which one was used.
1587 set_charset (ct
, contains8bit
);
1590 * Decide which transfer encoding to use.
1593 if (ct
->c_reqencoding
!= CE_UNKNOWN
)
1594 ct
->c_encoding
= ct
->c_reqencoding
;
1596 int wants_q_p
= (containsnul
|| linelen
|| linespace
|| checksw
);
1598 switch (ct
->c_type
) {
1601 ct
->c_encoding
= CE_QUOTED
;
1602 else if (contains8bit
)
1603 ct
->c_encoding
= CE_8BIT
;
1605 ct
->c_encoding
= CE_7BIT
;
1609 case CT_APPLICATION
:
1610 /* For application type, use base64, except when postscript */
1611 if (wants_q_p
|| contains8bit
) {
1612 if (ct
->c_subtype
== APPLICATION_POSTSCRIPT
)
1613 ct
->c_encoding
= CE_QUOTED
; /* historical */
1615 ct
->c_encoding
= CE_BASE64
;
1617 ct
->c_encoding
= CE_7BIT
;
1622 ct
->c_encoding
= contains8bit
? CE_8BIT
: CE_7BIT
;
1628 /* For audio, image, and video contents, just use base64 */
1629 ct
->c_encoding
= CE_BASE64
;
1634 return (boundaryclash
? NOTOK
: OK
);
1639 * Scan the content structures, and build header
1640 * fields that will need to be output into the
1645 build_headers (CT ct
, int header_encoding
)
1647 int cc
, mailbody
, extbody
, len
;
1648 char *np
, *vp
, buffer
[BUFSIZ
];
1649 CI ci
= &ct
->c_ctinfo
;
1652 * If message is type multipart, then add the multipart
1653 * boundary to the list of attribute/value pairs.
1655 if (ct
->c_type
== CT_MULTIPART
) {
1656 static int level
= 0; /* store nesting level */
1658 snprintf (buffer
, sizeof(buffer
), "%s%d", prefix
, level
++);
1659 add_param(&ci
->ci_first_pm
, &ci
->ci_last_pm
, "boundary", buffer
, 0);
1663 * Skip the output of Content-Type, parameters, content
1664 * description and disposition, and Content-ID if the
1665 * content is of type "message" and the rfc934 compatibility
1666 * flag is set (which means we are inside multipart/digest
1667 * and the switch -rfc934mode was given).
1669 if (ct
->c_type
== CT_MESSAGE
&& ct
->c_rfc934
)
1673 * output the content type and subtype
1675 np
= mh_xstrdup(TYPE_FIELD
);
1676 vp
= concat (" ", ci
->ci_type
, "/", ci
->ci_subtype
, NULL
);
1678 /* keep track of length of line */
1679 len
= strlen (TYPE_FIELD
) + strlen (ci
->ci_type
)
1680 + strlen (ci
->ci_subtype
) + 3;
1682 extbody
= ct
->c_type
== CT_MESSAGE
&& ct
->c_subtype
== MESSAGE_EXTERNAL
;
1683 mailbody
= extbody
&& ((struct exbody
*) ct
->c_ctparams
)->eb_body
;
1686 * Append the attribute/value pairs to
1687 * the end of the Content-Type line.
1690 if (ci
->ci_first_pm
) {
1691 char *s
= output_params(len
, ci
->ci_first_pm
, &len
, mailbody
);
1694 adios(NULL
, "Internal error: failed outputting Content-Type "
1702 * Append any RFC-822 comment to the end of
1703 * the Content-Type line.
1705 if (ci
->ci_comment
) {
1706 snprintf (buffer
, sizeof(buffer
), "(%s)", ci
->ci_comment
);
1707 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
1708 vp
= add ("\n\t", vp
);
1714 vp
= add (buffer
, vp
);
1717 vp
= add ("\n", vp
);
1718 add_header (ct
, np
, vp
);
1721 * output the Content-ID, unless disabled by -nocontentid. Note that
1722 * RFC 2045 always requires a Content-ID header for message/external-body
1725 if ((contentidsw
|| ct
->c_ctexbody
) && ct
->c_id
) {
1726 np
= mh_xstrdup(ID_FIELD
);
1727 vp
= concat (" ", ct
->c_id
, NULL
);
1728 add_header (ct
, np
, vp
);
1731 * output the Content-Description
1734 np
= mh_xstrdup(DESCR_FIELD
);
1735 vp
= concat (" ", ct
->c_descr
, NULL
);
1736 if (header_encoding
!= CE_8BIT
) {
1737 if (encode_rfc2047(DESCR_FIELD
, &vp
, header_encoding
, NULL
)) {
1738 adios(NULL
, "Unable to encode %s header", DESCR_FIELD
);
1741 add_header (ct
, np
, vp
);
1745 * output the Content-Disposition. If it's NULL but c_dispo_type is
1746 * set, then we need to build it.
1749 np
= mh_xstrdup(DISPO_FIELD
);
1750 vp
= concat (" ", ct
->c_dispo
, NULL
);
1751 add_header (ct
, np
, vp
);
1752 } else if (ct
->c_dispo_type
) {
1753 vp
= concat (" ", ct
->c_dispo_type
, NULL
);
1754 len
= strlen(DISPO_FIELD
) + strlen(vp
) + 1;
1755 np
= output_params(len
, ct
->c_dispo_first
, NULL
, 0);
1759 add_header (ct
, mh_xstrdup(DISPO_FIELD
), vp
);
1764 * If this is the internal content structure for a
1765 * "message/external", then we are done with the
1766 * headers (since it has no body).
1772 * output the Content-MD5
1775 np
= mh_xstrdup(MD5_FIELD
);
1776 vp
= calculate_digest (ct
, (ct
->c_encoding
== CE_QUOTED
) ? 1 : 0);
1777 add_header (ct
, np
, vp
);
1781 * output the Content-Transfer-Encoding
1782 * If using EAI and message body is 7-bit, force 8-bit C-T-E.
1784 if (header_encoding
== CE_8BIT
&& ct
->c_encoding
== CE_7BIT
) {
1785 ct
->c_encoding
= CE_8BIT
;
1788 switch (ct
->c_encoding
) {
1790 /* Nothing to output */
1794 np
= mh_xstrdup(ENCODING_FIELD
);
1795 vp
= concat (" ", "8bit", "\n", NULL
);
1796 add_header (ct
, np
, vp
);
1800 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1801 adios (NULL
, "internal error, invalid encoding");
1803 np
= mh_xstrdup(ENCODING_FIELD
);
1804 vp
= concat (" ", "quoted-printable", "\n", NULL
);
1805 add_header (ct
, np
, vp
);
1809 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1810 adios (NULL
, "internal error, invalid encoding");
1812 np
= mh_xstrdup(ENCODING_FIELD
);
1813 vp
= concat (" ", "base64", "\n", NULL
);
1814 add_header (ct
, np
, vp
);
1818 if (ct
->c_type
== CT_MESSAGE
)
1819 adios (NULL
, "internal error, invalid encoding");
1821 np
= mh_xstrdup(ENCODING_FIELD
);
1822 vp
= concat (" ", "binary", "\n", NULL
);
1823 add_header (ct
, np
, vp
);
1827 adios (NULL
, "unknown transfer encoding in content");
1832 * Additional content specific header processing
1834 switch (ct
->c_type
) {
1837 struct multipart
*m
;
1840 m
= (struct multipart
*) ct
->c_ctparams
;
1841 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1845 build_headers (p
, header_encoding
);
1851 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1854 e
= (struct exbody
*) ct
->c_ctparams
;
1855 build_headers (e
->eb_content
, header_encoding
);
1868 static char nib2b64
[0x40+1] =
1869 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1872 calculate_digest (CT ct
, int asciiP
)
1877 unsigned char digest
[16];
1878 unsigned char outbuf
[25];
1880 CE ce
= &ct
->c_cefile
;
1881 char *infilename
= ce
->ce_file
? ce
->ce_file
: ct
->c_file
;
1885 if ((in
= fopen (infilename
, "r")) == NULL
)
1886 adios (infilename
, "unable to open for reading");
1888 /* Initialize md5 context */
1889 MD5Init (&mdContext
);
1891 /* calculate md5 message digest */
1896 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1899 cp
= bufp
+ gotlen
- 1;
1900 if ((c
= *cp
) == '\n')
1903 MD5Update (&mdContext
, (unsigned char *) bufp
,
1904 (unsigned int) gotlen
);
1907 MD5Update (&mdContext
, (unsigned char *) "\r\n", 2);
1910 char buffer
[BUFSIZ
];
1911 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), in
)) > 0)
1912 MD5Update (&mdContext
, (unsigned char *) buffer
, (unsigned int) cc
);
1915 /* md5 finalization. Write digest and zero md5 context */
1916 MD5Final (digest
, &mdContext
);
1921 /* print debugging info */
1925 fprintf (stderr
, "MD5 digest=");
1926 for (ep
= (dp
= digest
) + sizeof(digest
) / sizeof(digest
[0]);
1928 fprintf (stderr
, "%02x", *dp
& 0xff);
1929 fprintf (stderr
, "\n");
1932 /* encode the digest using base64 */
1933 for (dp
= digest
, op
= (char *) outbuf
,
1934 cc
= sizeof(digest
) / sizeof(digest
[0]);
1935 cc
> 0; cc
-= 3, op
+= 4) {
1939 bits
= (*dp
++ & 0xff) << 16;
1941 bits
|= (*dp
++ & 0xff) << 8;
1943 bits
|= *dp
++ & 0xff;
1946 for (bp
= op
+ 4; bp
> op
; bits
>>= 6)
1947 *--bp
= nib2b64
[bits
& 0x3f];
1955 /* null terminate string */
1958 /* now make copy and return string */
1959 vp
= concat (" ", outbuf
, "\n", NULL
);
1964 * Set things up for the content structure for file "filename" that
1969 setup_attach_content(CT ct
, char *filename
)
1971 char *type
, *simplename
= r1bindex(filename
, '/');
1972 struct str2init
*s2i
;
1975 if (! (type
= mime_type(filename
))) {
1976 adios(NULL
, "Unable to determine MIME type of \"%s\"", filename
);
1980 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1981 * since we're just feeding it a MIME type we have to add those ourself.
1982 * Map that to a valid content-type label and call any initialization
1986 if (get_ctinfo(type
, ct
, 0) == NOTOK
)
1991 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
1992 if (strcasecmp(ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0)
1994 if (!s2i
->si_key
&& !uprf(ct
->c_ctinfo
.ci_type
, "X-"))
1998 * Make sure the type isn't incompatible with what we can handle
2001 switch (ct
->c_type
= s2i
->si_val
) {
2003 adios (NULL
, "multipart types must be specified by mhbuild directives");
2007 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "partial") == 0)
2008 adios(NULL
, "Sorry, %s/%s isn't supported", ct
->c_ctinfo
.ci_type
,
2009 ct
->c_ctinfo
.ci_subtype
);
2010 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "external-body") == 0)
2011 adios(NULL
, "external-body messages must be specified "
2012 "by mhbuild directives");
2017 * This sets the subtype, if it's significant
2019 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
2020 (*ct
->c_ctinitfnx
)(ct
);
2025 * Feed in a few attributes; specifically, the name attribute, the
2026 * content-description, and the content-disposition.
2029 for (pm
= ct
->c_ctinfo
.ci_first_pm
; pm
; pm
= pm
->pm_next
) {
2030 if (strcasecmp(pm
->pm_name
, "name") == 0) {
2031 mh_xfree(pm
->pm_value
);
2032 pm
->pm_value
= mh_xstrdup(simplename
);
2038 add_param(&ct
->c_ctinfo
.ci_first_pm
, &ct
->c_ctinfo
.ci_last_pm
,
2039 "name", simplename
, 0);
2041 ct
->c_descr
= mh_xstrdup(simplename
);
2042 ct
->c_descr
= add("\n", ct
->c_descr
);
2043 ct
->c_cefile
.ce_file
= mh_xstrdup(filename
);
2045 set_disposition (ct
);
2047 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename", simplename
, 0);
2051 * If disposition type hasn't already been set in ct:
2052 * Look for mhbuild-disposition-<type>/<subtype> entry
2053 * that specifies Content-Disposition type. Only
2054 * 'attachment' and 'inline' are allowed. Default to
2058 set_disposition (CT ct
) {
2059 if (ct
->c_dispo_type
== NULL
) {
2060 char *cp
= context_find_by_type ("disposition", ct
->c_ctinfo
.ci_type
,
2061 ct
->c_ctinfo
.ci_subtype
);
2063 if (cp
&& strcasecmp (cp
, "attachment") &&
2064 strcasecmp (cp
, "inline")) {
2065 inform("configuration problem: %s-disposition-%s%s%s specifies "
2066 "'%s' but only 'attachment' and 'inline' are allowed, "
2067 "continuing...", invo_name
,
2068 ct
->c_ctinfo
.ci_type
,
2069 ct
->c_ctinfo
.ci_subtype
? "/" : "",
2070 ct
->c_ctinfo
.ci_subtype
? ct
->c_ctinfo
.ci_subtype
: "",
2076 ct
->c_dispo_type
= mh_xstrdup(cp
);
2081 * Set text content charset if it was unspecified. contains8bit
2083 * 0: content does not contain 8-bit characters
2084 * 1: content contains 8-bit characters
2085 * -1: ignore content and use user's locale to determine charset
2088 set_charset (CT ct
, int contains8bit
) {
2089 if (ct
->c_type
== CT_TEXT
) {
2092 if (ct
->c_ctparams
== NULL
) {
2095 t
->tx_charset
= CHARSET_UNSPECIFIED
;
2097 t
= (struct text
*) ct
->c_ctparams
;
2100 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
2101 CI ci
= &ct
->c_ctinfo
;
2102 char *eightbitcharset
= write_charset_8bit();
2103 char *charset
= contains8bit
? eightbitcharset
: "us-ascii";
2105 if (contains8bit
== 1 &&
2106 strcasecmp (eightbitcharset
, "US-ASCII") == 0) {
2107 adios (NULL
, "Text content contains 8 bit characters, but "
2108 "character set is US-ASCII");
2111 add_param (&ci
->ci_first_pm
, &ci
->ci_last_pm
, "charset", charset
,
2114 t
->tx_charset
= CHARSET_SPECIFIED
;
2121 * Look at all of the replied-to message parts and expand any that
2122 * are matched by a pseudoheader. Except don't descend into
2126 expand_pseudoheaders (CT ct
, struct multipart
*m
, const char *infile
,
2127 const convert_list
*convert_head
) {
2128 /* text_plain_ct is used to concatenate all of the text/plain
2129 replies into one part, instead of having each one in a separate
2131 CT text_plain_ct
= NULL
;
2133 switch (ct
->c_type
) {
2134 case CT_MULTIPART
: {
2135 struct multipart
*mp
= (struct multipart
*) ct
->c_ctparams
;
2138 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
2141 /* The parts are in descending priority order (defined by
2142 RFC 2046 Sec. 5.1.4) because they were reversed by
2143 parse_mime (). So, stop looking for matches with
2144 immediate subparts after the first match of an
2146 for (part
= mp
->mp_parts
; ! matched
&& part
; part
= part
->mp_next
) {
2147 char *type_subtype
=
2148 concat (part
->mp_part
->c_ctinfo
.ci_type
, "/",
2149 part
->mp_part
->c_ctinfo
.ci_subtype
, NULL
);
2151 if (part
->mp_part
->c_type
== CT_MULTIPART
) {
2152 expand_pseudoheaders (part
->mp_part
, m
, infile
,
2155 const convert_list
*c
;
2157 for (c
= convert_head
; c
; c
= c
->next
) {
2158 if (! strcasecmp (type_subtype
, c
->type
)) {
2159 expand_pseudoheader (part
->mp_part
, &text_plain_ct
,
2161 c
->type
, c
->argstring
);
2167 free (type_subtype
);
2170 for (part
= mp
->mp_parts
; part
; part
= part
->mp_next
) {
2171 expand_pseudoheaders (part
->mp_part
, m
, infile
, convert_head
);
2178 char *type_subtype
=
2179 concat (ct
->c_ctinfo
.ci_type
, "/", ct
->c_ctinfo
.ci_subtype
,
2181 const convert_list
*c
;
2183 for (c
= convert_head
; c
; c
= c
->next
) {
2184 if (! strcasecmp (type_subtype
, c
->type
)) {
2185 expand_pseudoheader (ct
, &text_plain_ct
, m
, infile
, c
->type
,
2190 free (type_subtype
);
2198 * Expand a single pseudoheader. It's for the specified type.
2201 expand_pseudoheader (CT ct
, CT
*text_plain_ct
, struct multipart
*m
,
2202 const char *infile
, const char *type
,
2203 const char *argstring
) {
2205 FILE *reply_fp
= NULL
;
2206 char *convert
, *type_p
, *subtype_p
;
2207 char *convert_command
;
2208 char *charset
= NULL
;
2210 struct str2init
*s2i
;
2216 type_p
= getcpy (type
);
2217 if ((subtype_p
= strchr (type_p
, '/'))) {
2218 *subtype_p
++ = '\0';
2219 convert
= context_find_by_type ("convert", type_p
, subtype_p
);
2222 type_p
= concat ("mhbuild-convert-", type
, NULL
);
2223 convert
= context_find (type_p
);
2228 /* No mhbuild-convert- entry in mhn.defaults or profile
2232 /* reply_file is used to pass the output of the convert. */
2233 reply_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2235 concat (convert
, " ", argstring
? argstring
: "", " >", reply_file
,
2238 /* Convert here . . . */
2239 ct
->c_storeproc
= mh_xstrdup(convert_command
);
2240 ct
->c_umask
= ~m_gmprot ();
2242 if ((status
= show_content_aux (ct
, 0, convert_command
, NULL
, NULL
)) !=
2244 inform("store of %s content failed, continuing...", type
);
2246 free (convert_command
);
2248 /* Fill out the the new ct, reply_ct. */
2250 init_decoded_content (reply_ct
, infile
);
2252 if (extract_headers (reply_ct
, reply_file
, &reply_fp
) == NOTOK
) {
2254 inform("failed to extract headers from convert output in %s, "
2255 "continuing...", reply_file
);
2259 /* For text content only, see if it is 8-bit text. */
2260 if (reply_ct
->c_type
== CT_TEXT
) {
2263 if ((fd
= open (reply_file
, O_RDONLY
)) == NOTOK
||
2264 scan_input (fd
, &eightbit
) == NOTOK
) {
2266 inform("failed to read %s, continuing...", reply_file
);
2272 /* This sets reply_ct->c_ctparams, and reply_ct->c_termproc if the
2273 charset can't be handled natively. */
2274 for (s2i
= str2cts
; s2i
->si_key
; s2i
++) {
2275 if (strcasecmp(reply_ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0) {
2280 if ((reply_ct
->c_ctinitfnx
= s2i
->si_init
)) {
2281 (*reply_ct
->c_ctinitfnx
)(reply_ct
);
2285 get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1))) {
2286 /* The reply Content-Type had the charset. */
2289 set_charset (reply_ct
, -1);
2290 charset
= get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1);
2291 if (reply_ct
->c_reqencoding
== CE_UNKNOWN
&&
2292 reply_ct
->c_type
== CT_TEXT
) {
2293 /* Assume that 8bit is sufficient (for text). In other words,
2294 don't allow it to be encoded as quoted printable if lines
2295 are too long. This also sidesteps the check for whether
2296 it needs to be encoded as binary; instead, it relies on
2297 the applicable mhbuild-convert-text directive to ensure
2298 that the resultant text is not binary. */
2299 reply_ct
->c_reqencoding
= eightbit
? CE_8BIT
: CE_7BIT
;
2303 /* Concatenate text/plain parts. */
2304 if (reply_ct
->c_type
== CT_TEXT
&&
2305 reply_ct
->c_subtype
== TEXT_PLAIN
) {
2306 if (! *text_plain_ct
&& m
->mp_parts
&& m
->mp_parts
->mp_part
&&
2307 m
->mp_parts
->mp_part
->c_type
== CT_TEXT
&&
2308 m
->mp_parts
->mp_part
->c_subtype
== TEXT_PLAIN
) {
2309 *text_plain_ct
= m
->mp_parts
->mp_part
;
2310 /* Make sure that the charset is set in the text/plain
2312 set_charset (*text_plain_ct
, -1);
2313 if ((*text_plain_ct
)->c_reqencoding
== CE_UNKNOWN
) {
2314 /* Assume that 8bit is sufficient (for text). In other words,
2315 don't allow it to be encoded as quoted printable if lines
2316 are too long. This also sidesteps the check for whether
2317 it needs to be encoded as binary; instead, it relies on
2318 the applicable mhbuild-convert-text directive to ensure
2319 that the resultant text is not binary. */
2320 (*text_plain_ct
)->c_reqencoding
=
2321 eightbit
? CE_8BIT
: CE_7BIT
;
2325 if (*text_plain_ct
) {
2326 /* Only concatenate if the charsets are identical. */
2327 char *text_plain_ct_charset
=
2328 get_param ((*text_plain_ct
)->c_ctinfo
.ci_first_pm
, "charset",
2331 if (strcasecmp (text_plain_ct_charset
, charset
) == 0) {
2332 /* Append this text/plain reply to the first one.
2333 If there's a problem anywhere along the way,
2334 instead attach it is a separate part. */
2335 int text_plain_reply
=
2336 open ((*text_plain_ct
)->c_cefile
.ce_file
,
2337 O_WRONLY
| O_APPEND
);
2338 int addl_reply
= open (reply_file
, O_RDONLY
);
2340 if (text_plain_reply
!= NOTOK
&& addl_reply
!= NOTOK
) {
2341 /* Insert blank line before each addl part. */
2342 /* It would be nice not to do this for the first one. */
2343 if (write (text_plain_reply
, "\n", 1) == 1) {
2344 /* Copy the text from the new reply and
2345 then free its Content struct. */
2346 cpydata (addl_reply
, text_plain_reply
,
2347 (*text_plain_ct
)->c_cefile
.ce_file
,
2349 if (close (text_plain_reply
) == OK
&&
2350 close (addl_reply
) == OK
) {
2351 /* If appended text needed 8-bit but first text didn't,
2352 propagate the 8-bit indication. */
2353 if ((*text_plain_ct
)->c_reqencoding
== CE_7BIT
&&
2354 reply_ct
->c_reqencoding
== CE_8BIT
) {
2355 (*text_plain_ct
)->c_reqencoding
= CE_8BIT
;
2358 if (reply_fp
) { fclose (reply_fp
); }
2360 free_content (reply_ct
);
2367 *text_plain_ct
= reply_ct
;
2371 reply_ct
->c_cefile
.ce_file
= reply_file
;
2372 reply_ct
->c_cefile
.ce_fp
= reply_fp
;
2373 reply_ct
->c_cefile
.ce_unlink
= 1;
2375 /* Attach the new part to the parent multipart/mixed, "m". */
2377 part
->mp_part
= reply_ct
;
2381 for (p
= m
->mp_parts
; p
&& p
->mp_next
; p
= p
->mp_next
) { continue; }
2389 /* Extract any Content-Type header from beginning of convert output. */
2391 extract_headers (CT ct
, char *reply_file
, FILE **reply_fp
) {
2392 char *buffer
= NULL
, *cp
, *end_of_header
;
2393 int found_header
= 0;
2394 struct stat statbuf
;
2396 /* Read the convert reply from the file to memory. */
2397 if (stat (reply_file
, &statbuf
) == NOTOK
) {
2398 admonish (reply_file
, "failed to stat");
2399 goto failed_to_extract_ct
;
2402 buffer
= mh_xmalloc (statbuf
.st_size
+ 1);
2404 if ((*reply_fp
= fopen (reply_file
, "r+")) == NULL
||
2405 fread (buffer
, 1, (size_t) statbuf
.st_size
, *reply_fp
) <
2406 (size_t) statbuf
.st_size
) {
2407 admonish (reply_file
, "failed to read");
2408 goto failed_to_extract_ct
;
2410 buffer
[statbuf
.st_size
] = '\0';
2412 /* Look for a header in the convert reply. */
2413 if (strncasecmp (buffer
, TYPE_FIELD
, strlen (TYPE_FIELD
)) == 0 &&
2414 buffer
[strlen (TYPE_FIELD
)] == ':') {
2415 if ((end_of_header
= strstr (buffer
, "\r\n\r\n"))) {
2418 } else if ((end_of_header
= strstr (buffer
, "\n\n"))) {
2430 /* Truncate buffer to just the C-T. */
2431 *end_of_header
= '\0';
2432 n
= strlen (buffer
);
2434 if (get_ctinfo (buffer
+ 14, ct
, 0) != OK
) {
2435 inform("unable to get content info for reply, continuing...");
2436 goto failed_to_extract_ct
;
2439 /* Hack. Use parse_mime() to detect the type/subtype of the
2440 reply, which we'll use below. */
2441 tmp_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2442 if ((tmp_f
= fopen (tmp_file
, "w")) &&
2443 fwrite (buffer
, 1, n
, tmp_f
) == n
) {
2446 goto failed_to_extract_ct
;
2448 tmp_ct
= parse_mime (tmp_file
);
2451 /* The type and subtype were detected from the reply
2452 using parse_mime() above. */
2453 ct
->c_type
= tmp_ct
->c_type
;
2454 ct
->c_subtype
= tmp_ct
->c_subtype
;
2455 free_content (tmp_ct
);
2460 /* Rewrite the content without the header. */
2461 cp
= end_of_header
+ 1;
2464 if (fwrite (cp
, 1, statbuf
.st_size
- (cp
- buffer
), *reply_fp
) <
2465 (size_t) (statbuf
.st_size
- (cp
- buffer
))) {
2466 admonish (reply_file
, "failed to write");
2467 goto failed_to_extract_ct
;
2470 if (ftruncate (fileno (*reply_fp
), statbuf
.st_size
- (cp
- buffer
)) !=
2472 advise (reply_file
, "ftruncate");
2473 goto failed_to_extract_ct
;
2476 /* No header section, assume the reply is text/plain. */
2477 ct
->c_type
= CT_TEXT
;
2478 ct
->c_subtype
= TEXT_PLAIN
;
2479 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
) {
2480 /* This never should fail, but just in case. */
2481 adios (NULL
, "unable to get content info for reply");
2485 /* free_encoding() will close reply_fp, which is passed through
2486 ct->c_cefile.ce_fp. */
2490 failed_to_extract_ct
:
2491 if (*reply_fp
) { fclose (*reply_fp
); }