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
;
59 void content_error (char *, CT
, char *, ...);
62 int find_cache (CT
, int, int *, char *, char *, int);
66 void freects_done (int) NORETURN
;
67 void free_ctinfo (CT
);
68 void free_encoding (CT
, int);
73 static int init_decoded_content (CT
, const char *);
74 static void setup_attach_content(CT
, char *);
75 static void set_disposition (CT
);
76 static void set_charset (CT
, int);
77 static void expand_pseudoheaders (CT
, struct multipart
*, const char *,
78 const convert_list
*);
79 static void expand_pseudoheader (CT
, CT
*, struct multipart
*, const char *,
80 const char *, const char *);
81 static char *fgetstr (char *, int, FILE *);
82 static int user_content (FILE *, char *, CT
*, const char *infilename
);
83 static void set_id (CT
, int);
84 static int compose_content (CT
, int);
85 static int scan_content (CT
, size_t);
86 static int build_headers (CT
, int);
87 static char *calculate_digest (CT
, int);
88 static int extract_headers (CT
, char *, FILE **);
91 static unsigned char directives_stack
[32];
92 static unsigned int directives_index
;
94 static int do_direct(void)
96 return directives_stack
[directives_index
];
99 static void directive_onoff(int onoff
)
101 if (directives_index
>= sizeof(directives_stack
) - 1) {
102 fprintf(stderr
, "mhbuild: #on/off overflow, continuing\n");
105 directives_stack
[++directives_index
] = onoff
;
108 static void directive_init(int onoff
)
110 directives_index
= 0;
111 directives_stack
[0] = onoff
;
114 static void directive_pop(void)
116 if (directives_index
> 0)
119 fprintf(stderr
, "mhbuild: #pop underflow, continuing\n");
123 * Main routine for translating composition file
124 * into valid MIME message. It translates the draft
125 * into a content structure (actually a tree of content
126 * structures). This message then can be manipulated
127 * in various ways, including being output via
132 build_mime (char *infile
, int autobuild
, int dist
, int directives
,
133 int header_encoding
, size_t maxunencoded
, int verbose
)
136 char buf
[BUFSIZ
], name
[NAMESZ
];
143 m_getfld_state_t gstate
= 0;
144 struct attach_list
*attach_head
= NULL
, *attach_tail
= NULL
, *at_entry
;
145 convert_list
*convert_head
= NULL
, *convert_tail
= NULL
, *convert
;
147 directive_init(directives
);
149 umask (~m_gmprot ());
151 /* open the composition draft */
152 if ((in
= fopen (infile
, "r")) == NULL
)
153 adios (infile
, "unable to open for reading");
156 * Allocate space for primary (outside) content
161 * Allocate structure for handling decoded content
162 * for this part. We don't really need this, but
163 * allocate it to remain consistent.
165 init_decoded_content (ct
, infile
);
168 * Parse some of the header fields in the composition
169 * draft into the linked list of header fields for
170 * the new MIME message.
172 m_getfld_track_filepos (&gstate
, in
);
173 for (compnum
= 1;;) {
174 int bufsz
= sizeof buf
;
175 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
180 /* abort if draft has Mime-Version or C-T-E header field */
181 if (strcasecmp (name
, VRSN_FIELD
) == 0 ||
182 strcasecmp (name
, ENCODING_FIELD
) == 0) {
188 adios (NULL
, "draft shouldn't contain %s: field", name
);
191 /* ignore any Content-Type fields in the header */
192 if (!strcasecmp (name
, TYPE_FIELD
)) {
193 while (state
== FLDPLUS
) {
195 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
200 /* get copies of the buffers */
201 np
= mh_xstrdup(name
);
202 vp
= mh_xstrdup(buf
);
204 /* if necessary, get rest of field */
205 while (state
== FLDPLUS
) {
207 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
208 vp
= add (buf
, vp
); /* add to previous value */
212 * Now add the header data to the list, unless it's an attach
213 * header; in that case, add it to our attach list
216 if (strcasecmp(ATTACH_FIELD
, np
) == 0 ||
217 strcasecmp(ATTACH_FIELD_ALT
, np
) == 0) {
218 struct attach_list
*entry
;
219 char *s
= vp
, *e
= vp
+ strlen(vp
) - 1;
223 * Make sure we can find the start of this filename.
224 * If it's blank, we skip completely. Otherwise, strip
225 * off any leading spaces and trailing newlines.
228 while (isspace((unsigned char) *s
))
231 while (e
> s
&& *e
== '\n')
240 entry
->filename
= mh_xstrdup(s
);
245 attach_tail
->next
= entry
;
248 attach_head
= attach_tail
= entry
;
250 } else if (strncasecmp(MHBUILD_FILE_PSEUDOHEADER
, np
,
251 strlen (MHBUILD_FILE_PSEUDOHEADER
)) == 0) {
253 * Nmh-mhbuild-file-text/calendar: /home/user/Mail/inbox/9
255 char *type
= np
+ strlen (MHBUILD_FILE_PSEUDOHEADER
);
258 /* vp should begin with a space because m_getfld()
259 includes the space after the colon in buf. */
260 while (isspace((unsigned char) *filename
)) { ++filename
; }
261 /* Trim trailing newline and any other whitespace. */
264 for (convert
= convert_head
; convert
; convert
= convert
->next
) {
265 if (strcasecmp (convert
->type
, type
) == 0) { break; }
268 if (convert
->filename
&&
269 strcasecmp (convert
->filename
, filename
)) {
270 adios (NULL
, "Multiple %s headers with different files"
271 " not allowed", type
);
273 convert
->filename
= mh_xstrdup(filename
);
277 convert
->filename
= mh_xstrdup(filename
);
278 convert
->type
= mh_xstrdup(type
);
281 convert_tail
->next
= convert
;
283 convert_head
= convert
;
285 convert_tail
= convert
;
290 } else if (strncasecmp(MHBUILD_ARGS_PSEUDOHEADER
, np
,
291 strlen (MHBUILD_ARGS_PSEUDOHEADER
)) == 0) {
293 * Nmh-mhbuild-args-text/calendar: -reply accept
295 char *type
= np
+ strlen (MHBUILD_ARGS_PSEUDOHEADER
);
296 char *argstring
= vp
;
298 /* vp should begin with a space because m_getfld()
299 includes the space after the colon in buf. */
300 while (isspace((unsigned char) *argstring
)) { ++argstring
; }
301 /* Trim trailing newline and any other whitespace. */
304 for (convert
= convert_head
; convert
; convert
= convert
->next
) {
305 if (strcasecmp (convert
->type
, type
) == 0) { break; }
308 if (convert
->argstring
&&
309 strcasecmp (convert
->argstring
, argstring
)) {
310 adios (NULL
, "Multiple %s headers with different "
311 "argstrings not allowed", type
);
313 convert
->argstring
= mh_xstrdup(argstring
);
317 convert
->type
= mh_xstrdup(type
);
318 convert
->argstring
= mh_xstrdup(argstring
);
321 convert_tail
->next
= convert
;
323 convert_head
= convert
;
325 convert_tail
= convert
;
331 add_header (ct
, np
, vp
);
335 /* if this wasn't the last header field, then continue */
339 fseek (in
, (long) (-strlen (buf
)), SEEK_CUR
);
346 adios (NULL
, "message format error in component #%d", compnum
);
349 adios (NULL
, "getfld() returned %d", state
);
353 m_getfld_state_destroy (&gstate
);
355 if (header_encoding
!= CE_8BIT
) {
357 * Iterate through the list of headers and call the function to MIME-ify
361 for (hp
= ct
->c_first_hf
; hp
!= NULL
; hp
= hp
->next
) {
362 if (encode_rfc2047(hp
->name
, &hp
->value
, header_encoding
, NULL
)) {
363 adios(NULL
, "Unable to encode header \"%s\"", hp
->name
);
369 * Now add the MIME-Version header field
370 * to the list of header fields.
374 np
= mh_xstrdup(VRSN_FIELD
);
375 vp
= concat (" ", VRSN_VALUE
, "\n", NULL
);
376 add_header (ct
, np
, vp
);
380 * We initally assume we will find multiple contents in the
381 * draft. So create a multipart/mixed content to hold everything.
382 * We can remove this later, if it is not needed.
384 if (get_ctinfo ("multipart/mixed", ct
, 0) == NOTOK
)
386 ct
->c_type
= CT_MULTIPART
;
387 ct
->c_subtype
= MULTI_MIXED
;
390 ct
->c_ctparams
= (void *) m
;
394 * read and parse the composition file
395 * and the directives it contains.
397 while (fgetstr (buf
, sizeof(buf
) - 1, in
)) {
401 if (user_content (in
, buf
, &p
, infile
) == DONE
) {
402 inform("ignoring spurious #end, continuing...");
415 * Add any Attach headers to the list of MIME parts at the end of the
419 for (at_entry
= attach_head
; at_entry
; ) {
420 struct attach_list
*at_prev
= at_entry
;
424 if (access(at_entry
->filename
, R_OK
) != 0) {
425 adios("reading", "Unable to open %s for", at_entry
->filename
);
429 init_decoded_content(p
, infile
);
432 * Initialize our content structure based on the filename,
433 * and fill in all of the relevant fields. Also place MIME
434 * parameters in the attributes array.
437 setup_attach_content(p
, at_entry
->filename
);
444 at_entry
= at_entry
->next
;
445 free(at_prev
->filename
);
450 * Handle the mhbuild pseudoheaders, which deal with specific
459 /* In case there are multiple calls that land here, prevent leak. */
460 for (ctp
= cts
; ctp
&& *ctp
; ++ctp
) { free_content (*ctp
); }
463 /* Extract the type part (as a CT) from filename. */
464 cts
= mh_xcalloc(2, sizeof *cts
);
465 if (! (cts
[0] = parse_mime (convert_head
->filename
))) {
466 adios (NULL
, "failed to parse %s", convert_head
->filename
);
469 expand_pseudoheaders (cts
[0], m
, infile
, convert_head
);
471 /* Free the convert list. */
472 for (convert
= convert_head
; convert
; convert
= next
) {
473 next
= convert
->next
;
474 free (convert
->type
);
475 free (convert
->filename
);
476 free (convert
->argstring
);
483 * To allow for empty message bodies, if we've found NO content at all
484 * yet cook up an empty text/plain part.
493 init_decoded_content(p
, infile
);
495 if (get_ctinfo ("text/plain", p
, 0) == NOTOK
)
499 p
->c_subtype
= TEXT_PLAIN
;
500 p
->c_encoding
= CE_7BIT
;
502 * Sigh. ce_file contains the "decoded" contents of this part.
503 * So this seems like the best option available since we're going
504 * to call scan_content() on this.
506 p
->c_cefile
.ce_file
= mh_xstrdup("/dev/null");
507 p
->c_begin
= ftell(in
);
508 p
->c_end
= ftell(in
);
511 t
->tx_charset
= CHARSET_SPECIFIED
;
520 * close the composition draft since
521 * it's not needed any longer.
526 * If only one content was found, then remove and
527 * free the outer multipart content.
529 if (!m
->mp_parts
->mp_next
) {
532 p
= m
->mp_parts
->mp_part
;
533 m
->mp_parts
->mp_part
= NULL
;
535 /* move header fields */
536 p
->c_first_hf
= ct
->c_first_hf
;
537 p
->c_last_hf
= ct
->c_last_hf
;
538 ct
->c_first_hf
= NULL
;
539 ct
->c_last_hf
= NULL
;
548 * Fill out, or expand directives. Parse and execute
549 * commands specified by profile composition strings.
551 compose_content (ct
, verbose
);
553 if ((cp
= strchr(prefix
, 'a')) == NULL
)
554 adios (NULL
, "internal error(4)");
557 * If using EAI, force 8-bit charset.
559 if (header_encoding
== CE_8BIT
) {
564 * Scan the contents. Choose a transfer encoding, and
565 * check if prefix for multipart boundary clashes with
566 * any of the contents.
568 while (scan_content (ct
, maxunencoded
) == NOTOK
) {
573 adios (NULL
, "giving up trying to find a unique delimiter string");
579 /* Build the rest of the header field structures */
581 build_headers (ct
, header_encoding
);
588 * Set up structures for placing unencoded
589 * content when building parts.
593 init_decoded_content (CT ct
, const char *filename
)
595 ct
->c_ceopenfnx
= open7Bit
; /* since unencoded */
596 ct
->c_ceclosefnx
= close_encoding
;
597 ct
->c_cesizefnx
= NULL
; /* since unencoded */
598 ct
->c_encoding
= CE_7BIT
; /* Seems like a reasonable default */
599 ct
->c_file
= add(filename
, NULL
);
606 fgetstr (char *s
, int n
, FILE *stream
)
612 for (cp
= s
; cp
< ep
;) {
615 if (!fgets (cp
, n
, stream
))
616 return cp
== s
? NULL
: s
; /* "\\\nEOF" ignored. */
618 if (! do_direct() || (cp
== s
&& *cp
!= '#'))
619 return s
; /* Plaintext line. */
623 break; /* Can't contain "\\\n". */
624 cp
+= len
- 1; /* Just before NUL. */
625 if (*cp
-- != '\n' || *cp
!= '\\')
627 *cp
= '\0'; /* Erase the trailing "\\\n". */
631 if (strcmp(s
, "#on\n") == 0) {
633 } else if (strcmp(s
, "#off\n") == 0) {
635 } else if (strcmp(s
, "#pop\n") == 0) {
645 * Parse the composition draft for text and directives.
646 * Do initial setup of Content structure.
650 user_content (FILE *in
, char *buf
, CT
*ctp
, const char *infilename
)
658 struct str2init
*s2i
;
663 if (buf
[0] == '\n' || (do_direct() && strcmp (buf
, "#\n") == 0)) {
668 /* allocate basic Content structure */
672 /* allocate basic structure for handling decoded content */
673 init_decoded_content (ct
, infilename
);
680 * Handle inline text. Check if line
681 * is one of the following forms:
683 * 1) doesn't begin with '#' (implicit directive)
684 * 2) begins with "##" (implicit directive)
685 * 3) begins with "#<"
687 if (!do_direct() || buf
[0] != '#' || buf
[1] == '#' || buf
[1] == '<') {
691 char content
[BUFSIZ
];
695 if ((cp
= m_mktemp2(NULL
, invo_name
, NULL
, &out
)) == NULL
) {
696 adios("mhbuildsbr", "unable to create temporary file in %s",
700 /* use a temp file to collect the plain text lines */
701 ce
->ce_file
= mh_xstrdup(cp
);
704 if (do_direct() && (buf
[0] == '#' && buf
[1] == '<')) {
705 strncpy (content
, buf
+ 2, sizeof(content
));
711 /* the directive is implicit */
712 strncpy (content
, "text/plain", sizeof(content
));
714 strncpy (buffer
, (!do_direct() || buf
[0] != '#') ? buf
: buf
+ 1, sizeof(buffer
));
718 if (headers
>= 0 && do_direct() && uprf (buffer
, DESCR_FIELD
)
719 && buffer
[i
= strlen (DESCR_FIELD
)] == ':') {
723 ct
->c_descr
= add (buffer
+ i
+ 1, ct
->c_descr
);
724 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
725 adios (NULL
, "end-of-file after %s: field in plaintext", DESCR_FIELD
);
733 adios (NULL
, "#-directive after %s: field in plaintext", DESCR_FIELD
);
741 if (headers
>= 0 && do_direct() && uprf (buffer
, DISPO_FIELD
)
742 && buffer
[i
= strlen (DISPO_FIELD
)] == ':') {
746 ct
->c_dispo
= add (buffer
+ i
+ 1, ct
->c_dispo
);
747 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
748 adios (NULL
, "end-of-file after %s: field in plaintext", DISPO_FIELD
);
756 adios (NULL
, "#-directive after %s: field in plaintext", DISPO_FIELD
);
764 if (headers
!= 1 || buffer
[0] != '\n')
770 if ((cp
= fgetstr (buffer
, sizeof(buffer
) - 1, in
)) == NULL
)
772 if (do_direct() && buffer
[0] == '#') {
775 if (buffer
[1] != '#')
777 for (cp
= (bp
= buffer
) + 1; *cp
; cp
++)
784 ct
->c_end
= ftell (out
);
787 /* parse content type */
788 if (get_ctinfo (content
, ct
, inlineD
) == NOTOK
)
791 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
792 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
794 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
798 * check type specified (possibly implicitly)
800 switch (ct
->c_type
= s2i
->si_val
) {
802 if (!strcasecmp (ci
->ci_subtype
, "rfc822")) {
803 ct
->c_encoding
= CE_7BIT
;
808 adios (NULL
, "it doesn't make sense to define an in-line %s content",
809 ct
->c_type
== CT_MESSAGE
? "message" : "multipart");
814 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
815 (*ct
->c_ctinitfnx
) (ct
);
820 fseek (in
, pos
, SEEK_SET
);
825 * If we've reached this point, the next line
826 * must be some type of explicit directive.
829 /* check if directive is external-type */
830 extrnal
= (buf
[1] == '@');
832 /* parse directive */
833 if (get_ctinfo (buf
+ (extrnal
? 2 : 1), ct
, 1) == NOTOK
)
836 /* check directive against the list of MIME types */
837 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
838 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
842 * Check if the directive specified a valid type.
843 * This will happen if it was one of the following forms:
850 adios (NULL
, "missing subtype in \"#%s\"", ci
->ci_type
);
852 switch (ct
->c_type
= s2i
->si_val
) {
854 adios (NULL
, "use \"#begin ... #end\" instead of \"#%s/%s\"",
855 ci
->ci_type
, ci
->ci_subtype
);
859 if (!strcasecmp (ci
->ci_subtype
, "partial"))
860 adios (NULL
, "sorry, \"#%s/%s\" isn't supported",
861 ci
->ci_type
, ci
->ci_subtype
);
862 if (!strcasecmp (ci
->ci_subtype
, "external-body"))
863 adios (NULL
, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
864 ci
->ci_type
, ci
->ci_subtype
);
867 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
868 ci
->ci_type
, ci
->ci_subtype
);
872 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
873 (*ct
->c_ctinitfnx
) (ct
);
878 * #@type/subtype (external types directive)
885 adios (NULL
, "need external information for \"#@%s/%s\"",
886 ci
->ci_type
, ci
->ci_subtype
);
889 snprintf (buffer
, sizeof(buffer
), "message/external-body; %s", ci
->ci_magic
);
894 * Since we are using the current Content structure to
895 * hold information about the type of the external
896 * reference, we need to create another Content structure
897 * for the message/external-body to wrap it in.
900 init_decoded_content(ct
, infilename
);
902 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
904 ct
->c_type
= CT_MESSAGE
;
905 ct
->c_subtype
= MESSAGE_EXTERNAL
;
908 ct
->c_ctparams
= (void *) e
;
914 if (params_external (ct
, 1) == NOTOK
)
920 /* Handle [file] argument */
922 /* check if specifies command to execute */
923 if (*ci
->ci_magic
== '|' || *ci
->ci_magic
== '!') {
924 for (cp
= ci
->ci_magic
+ 1; isspace ((unsigned char) *cp
); cp
++)
927 adios (NULL
, "empty pipe command for #%s directive", ci
->ci_type
);
932 /* record filename of decoded contents */
933 ce
->ce_file
= ci
->ci_magic
;
934 if (access (ce
->ce_file
, R_OK
) == NOTOK
)
935 adios ("reading", "unable to access %s for", ce
->ce_file
);
936 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
937 ct
->c_end
= (long) st
.st_size
;
944 * No [file] argument, so check profile for
945 * method to compose content.
947 cp
= context_find_by_type ("compose", ci
->ci_type
, ci
->ci_subtype
);
949 content_error (NULL
, ct
, "don't know how to compose content");
952 ci
->ci_magic
= mh_xstrdup(cp
);
957 adios (NULL
, "external definition not allowed for \"#%s\"", ci
->ci_type
);
961 * #forw [+folder] [msgs]
963 if (!strcasecmp (ci
->ci_type
, "forw")) {
965 char *folder
, *arguments
[MAXARGS
];
969 ap
= brkstring (ci
->ci_magic
, " ", "\n");
970 copyip (ap
, arguments
, MAXARGS
);
972 arguments
[0] = "cur";
977 /* search the arguments for a folder name */
978 for (ap
= arguments
; *ap
; ap
++) {
980 if (*cp
== '+' || *cp
== '@') {
982 adios (NULL
, "only one folder per #forw directive");
984 folder
= pluspath (cp
);
988 /* else, use the current folder */
990 folder
= mh_xstrdup(getfolder(1));
992 if (!(mp
= folder_read (folder
, 0)))
993 adios (NULL
, "unable to read folder %s", folder
);
994 for (ap
= arguments
; *ap
; ap
++) {
996 if (*cp
!= '+' && *cp
!= '@')
997 if (!m_convert (mp
, cp
))
1004 * If there is more than one message to include, make this
1005 * a content of type "multipart/digest" and insert each message
1006 * as a subpart. If there is only one message, then make this
1007 * a content of type "message/rfc822".
1009 if (mp
->numsel
> 1) {
1010 /* we are forwarding multiple messages */
1011 if (get_ctinfo ("multipart/digest", ct
, 0) == NOTOK
)
1013 ct
->c_type
= CT_MULTIPART
;
1014 ct
->c_subtype
= MULTI_DIGEST
;
1017 ct
->c_ctparams
= (void *) m
;
1020 for (msgnum
= mp
->lowsel
; msgnum
<= mp
->hghsel
; msgnum
++) {
1021 if (is_selected(mp
, msgnum
)) {
1027 init_decoded_content (p
, infilename
);
1029 if (get_ctinfo ("message/rfc822", p
, 0) == NOTOK
)
1031 p
->c_type
= CT_MESSAGE
;
1032 p
->c_subtype
= MESSAGE_RFC822
;
1034 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1035 pe
->ce_file
= mh_xstrdup(buffer
);
1036 if (listsw
&& stat (pe
->ce_file
, &st
) != NOTOK
)
1037 p
->c_end
= (long) st
.st_size
;
1041 pp
= &part
->mp_next
;
1046 /* we are forwarding one message */
1047 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
1049 ct
->c_type
= CT_MESSAGE
;
1050 ct
->c_subtype
= MESSAGE_RFC822
;
1052 msgnum
= mp
->lowsel
;
1053 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1054 ce
->ce_file
= mh_xstrdup(buffer
);
1055 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
1056 ct
->c_end
= (long) st
.st_size
;
1059 folder_free (mp
); /* free folder/message structure */
1066 if (!strcasecmp (ci
->ci_type
, "end")) {
1073 * #begin [ alternative | parallel ]
1075 if (!strcasecmp (ci
->ci_type
, "begin")) {
1076 if (!ci
->ci_magic
) {
1078 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1079 } else if (!strcasecmp (ci
->ci_magic
, "alternative")) {
1080 vrsn
= MULTI_ALTERNATE
;
1081 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1082 } else if (!strcasecmp (ci
->ci_magic
, "parallel")) {
1083 vrsn
= MULTI_PARALLEL
;
1084 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1085 } else if (uprf (ci
->ci_magic
, "digest")) {
1088 vrsn
= MULTI_UNKNOWN
;
1093 snprintf (buffer
, sizeof(buffer
), "multipart/%s", cp
);
1094 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
1096 ct
->c_type
= CT_MULTIPART
;
1097 ct
->c_subtype
= vrsn
;
1100 ct
->c_ctparams
= (void *) m
;
1103 while (fgetstr (buffer
, sizeof(buffer
) - 1, in
)) {
1107 if (user_content (in
, buffer
, &p
, infilename
) == DONE
) {
1109 adios (NULL
, "empty \"#begin ... #end\" sequence");
1117 pp
= &part
->mp_next
;
1120 inform("premature end-of-file, missing #end, continuing...");
1127 adios (NULL
, "unknown directive \"#%s\"", ci
->ci_type
);
1128 return NOTOK
; /* NOT REACHED */
1133 set_id (CT ct
, int top
)
1135 char contentid
[BUFSIZ
];
1137 static time_t clock
= 0;
1138 static char *msgfmt
;
1142 snprintf (contentid
, sizeof(contentid
), "%s\n", message_id (clock
, 1));
1144 msgfmt
= mh_xstrdup(contentid
);
1146 snprintf (contentid
, sizeof(contentid
), msgfmt
, top
? 0 : ++partno
);
1147 ct
->c_id
= mh_xstrdup(contentid
);
1152 * Fill out, or expand the various contents in the composition
1153 * draft. Read-in any necessary files. Parse and execute any
1154 * commands specified by profile composition strings.
1158 compose_content (CT ct
, int verbose
)
1160 CE ce
= &ct
->c_cefile
;
1162 switch (ct
->c_type
) {
1167 char partnam
[BUFSIZ
];
1168 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1172 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1173 pp
= partnam
+ strlen (partnam
);
1178 /* first, we call compose_content on all the subparts */
1179 for (part
= m
->mp_parts
, partnum
= 1; part
; part
= part
->mp_next
, partnum
++) {
1180 CT p
= part
->mp_part
;
1182 sprintf (pp
, "%d", partnum
);
1183 p
->c_partno
= mh_xstrdup(partnam
);
1184 if (compose_content (p
, verbose
) == NOTOK
)
1189 * If the -rfc934mode switch is given, then check all
1190 * the subparts of a multipart/digest. If they are all
1191 * message/rfc822, then mark this content and all
1192 * subparts with the rfc934 compatibility mode flag.
1194 if (rfc934sw
&& ct
->c_subtype
== MULTI_DIGEST
) {
1197 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1198 CT p
= part
->mp_part
;
1200 if (p
->c_subtype
!= MESSAGE_RFC822
) {
1205 ct
->c_rfc934
= is934
;
1206 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1207 CT p
= part
->mp_part
;
1209 if ((p
->c_rfc934
= is934
))
1215 ct
->c_end
= (partnum
= strlen (prefix
) + 2) + 2;
1219 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
)
1220 ct
->c_end
+= part
->mp_part
->c_end
+ partnum
;
1226 /* Nothing to do for type message */
1230 * Discrete types (text/application/audio/image/video)
1235 int i
, xstdout
, len
, buflen
;
1237 char *vec
[4], buffer
[BUFSIZ
];
1239 CI ci
= &ct
->c_ctinfo
;
1242 if (!(cp
= ci
->ci_magic
))
1243 adios (NULL
, "internal error(5)");
1245 if ((tfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1246 adios("mhbuildsbr", "unable to create temporary file in %s",
1249 ce
->ce_file
= mh_xstrdup(tfile
);
1254 /* Get buffer ready to go */
1257 buflen
= sizeof(buffer
);
1260 * Parse composition string into buffer
1262 for ( ; *cp
; cp
++) {
1267 /* insert parameters from directive */
1271 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1272 snprintf (bp
, buflen
, "%s%s=\"%s\"", s
,
1273 pm
->pm_name
, get_param_value(pm
, '?'));
1283 /* %f, and stdout is not-redirected */
1289 * insert temporary filename where
1290 * content should be written
1292 snprintf (bp
, buflen
, "%s", ce
->ce_file
);
1296 /* insert content subtype */
1297 strncpy (bp
, ci
->ci_subtype
, buflen
);
1301 /* insert character % */
1322 printf ("composing content %s/%s from command\n\t%s\n",
1323 ci
->ci_type
, ci
->ci_subtype
, buffer
);
1325 fflush (stdout
); /* not sure if need for -noverbose */
1332 if ((out
= fopen (ce
->ce_file
, "w")) == NULL
)
1333 adios (ce
->ce_file
, "unable to open for writing");
1335 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
> 5; i
++)
1339 adios ("fork", "unable to fork");
1344 dup2 (fileno (out
), 1);
1345 close (fileno (out
));
1346 execvp ("/bin/sh", vec
);
1347 fprintf (stderr
, "unable to exec ");
1354 if (pidXwait(child_id
, NULL
))
1360 /* Check size of file */
1361 if (listsw
&& ct
->c_end
== 0L) {
1364 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1365 ct
->c_end
= (long) st
.st_size
;
1377 * 1) choose a transfer encoding.
1378 * 2) check for clashes with multipart boundary string.
1379 * 3) for text content, figure out which character set is being used.
1381 * If there is a clash with one of the contents and the multipart boundary,
1382 * this function will exit with NOTOK. This will cause the scanning process
1383 * to be repeated with a different multipart boundary. It is possible
1384 * (although highly unlikely) that this scan will be repeated multiple times.
1388 scan_content (CT ct
, size_t maxunencoded
)
1391 int check8bit
= 0, contains8bit
= 0; /* check if contains 8bit data */
1392 int checknul
= 0, containsnul
= 0; /* check if contains NULs */
1393 int checklinelen
= 0, linelen
= 0; /* check for long lines */
1394 int checkllinelen
= 0; /* check for extra-long lines */
1395 int checkboundary
= 0, boundaryclash
= 0; /* check if clashes with multipart boundary */
1396 int checklinespace
= 0, linespace
= 0; /* check if any line ends with space */
1401 struct text
*t
= NULL
;
1403 CE ce
= &ct
->c_cefile
;
1406 * handle multipart by scanning all subparts
1407 * and then checking their encoding.
1409 if (ct
->c_type
== CT_MULTIPART
) {
1410 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1413 /* initially mark the domain of enclosing multipart as 7bit */
1414 ct
->c_encoding
= CE_7BIT
;
1416 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1417 CT p
= part
->mp_part
;
1419 if (scan_content (p
, maxunencoded
) == NOTOK
) /* choose encoding for subpart */
1422 /* if necessary, enlarge encoding for enclosing multipart */
1423 if (p
->c_encoding
== CE_BINARY
)
1424 ct
->c_encoding
= CE_BINARY
;
1425 if (p
->c_encoding
== CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
)
1426 ct
->c_encoding
= CE_8BIT
;
1433 * Decide what to check while scanning this content. Note that
1434 * for text content we always check for 8bit characters if the
1435 * charset is unspecified, because that controls whether or not the
1436 * character set is us-ascii or retrieved from the locale. And
1437 * we check even if the charset is specified, to allow setting
1438 * the proper Content-Transfer-Encoding.
1441 if (ct
->c_type
== CT_TEXT
) {
1442 t
= (struct text
*) ct
->c_ctparams
;
1443 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
1449 switch (ct
->c_reqencoding
) {
1460 /* Use the default rules based on content-type */
1461 switch (ct
->c_type
) {
1465 if (ct
->c_subtype
== TEXT_PLAIN
) {
1472 case CT_APPLICATION
:
1484 /* don't check anything for message/external */
1485 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1498 * Don't check anything for these types,
1499 * since we are forcing use of base64, unless
1500 * the content-type was specified by a mhbuild directive.
1511 * Scan the unencoded content
1513 if (check8bit
|| checklinelen
|| checklinespace
|| checkboundary
||
1514 checkllinelen
|| checknul
) {
1515 if ((in
= fopen (ce
->ce_file
, "r")) == NULL
)
1516 adios (ce
->ce_file
, "unable to open for reading");
1517 prefix_len
= strlen (prefix
);
1519 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1521 * Check for 8bit and NUL data.
1523 for (cp
= bufp
; (check8bit
|| checknul
) &&
1524 cp
< bufp
+ gotlen
; cp
++) {
1525 if (!isascii ((unsigned char) *cp
)) {
1527 check8bit
= 0; /* no need to keep checking */
1531 checknul
= 0; /* no need to keep checking */
1536 * Check line length.
1538 if (checklinelen
&& ((size_t)gotlen
> maxunencoded
+ 1)) {
1540 checklinelen
= 0; /* no need to keep checking */
1544 * RFC 5322 specifies that a message cannot contain a line
1545 * greater than 998 characters (excluding the CRLF). If we
1546 * get one of those lines and linelen is NOT set, then abort.
1549 if (checkllinelen
&& !linelen
&&
1550 (gotlen
> MAXLONGLINE
+ 1)) {
1551 adios(NULL
, "Line in content exceeds maximum line limit (%d)",
1556 * Check if line ends with a space.
1558 if (checklinespace
&& (cp
= bufp
+ gotlen
- 2) > bufp
&&
1559 isspace ((unsigned char) *cp
)) {
1561 checklinespace
= 0; /* no need to keep checking */
1565 * Check if content contains a line that clashes
1566 * with our standard boundary for multipart messages.
1568 if (checkboundary
&& bufp
[0] == '-' && bufp
[1] == '-') {
1569 for (cp
= bufp
+ gotlen
- 1; cp
>= bufp
; cp
--)
1570 if (!isspace ((unsigned char) *cp
))
1573 if (!strncmp(bufp
+ 2, prefix
, prefix_len
) &&
1574 isdigit((unsigned char) bufp
[2 + prefix_len
])) {
1576 checkboundary
= 0; /* no need to keep checking */
1585 * If the content is text and didn't specify a character set,
1586 * we need to figure out which one was used.
1588 set_charset (ct
, contains8bit
);
1591 * Decide which transfer encoding to use.
1594 if (ct
->c_reqencoding
!= CE_UNKNOWN
)
1595 ct
->c_encoding
= ct
->c_reqencoding
;
1597 int wants_q_p
= (containsnul
|| linelen
|| linespace
|| checksw
);
1599 switch (ct
->c_type
) {
1602 ct
->c_encoding
= CE_QUOTED
;
1603 else if (contains8bit
)
1604 ct
->c_encoding
= CE_8BIT
;
1606 ct
->c_encoding
= CE_7BIT
;
1610 case CT_APPLICATION
:
1611 /* For application type, use base64, except when postscript */
1612 if (wants_q_p
|| contains8bit
) {
1613 if (ct
->c_subtype
== APPLICATION_POSTSCRIPT
)
1614 ct
->c_encoding
= CE_QUOTED
; /* historical */
1616 ct
->c_encoding
= CE_BASE64
;
1618 ct
->c_encoding
= CE_7BIT
;
1623 ct
->c_encoding
= contains8bit
? CE_8BIT
: CE_7BIT
;
1629 /* For audio, image, and video contents, just use base64 */
1630 ct
->c_encoding
= CE_BASE64
;
1635 return (boundaryclash
? NOTOK
: OK
);
1640 * Scan the content structures, and build header
1641 * fields that will need to be output into the
1646 build_headers (CT ct
, int header_encoding
)
1648 int cc
, mailbody
, extbody
, len
;
1649 char *np
, *vp
, buffer
[BUFSIZ
];
1650 CI ci
= &ct
->c_ctinfo
;
1653 * If message is type multipart, then add the multipart
1654 * boundary to the list of attribute/value pairs.
1656 if (ct
->c_type
== CT_MULTIPART
) {
1657 static int level
= 0; /* store nesting level */
1659 snprintf (buffer
, sizeof(buffer
), "%s%d", prefix
, level
++);
1660 add_param(&ci
->ci_first_pm
, &ci
->ci_last_pm
, "boundary", buffer
, 0);
1664 * Skip the output of Content-Type, parameters, content
1665 * description and disposition, and Content-ID if the
1666 * content is of type "message" and the rfc934 compatibility
1667 * flag is set (which means we are inside multipart/digest
1668 * and the switch -rfc934mode was given).
1670 if (ct
->c_type
== CT_MESSAGE
&& ct
->c_rfc934
)
1674 * output the content type and subtype
1676 np
= mh_xstrdup(TYPE_FIELD
);
1677 vp
= concat (" ", ci
->ci_type
, "/", ci
->ci_subtype
, NULL
);
1679 /* keep track of length of line */
1680 len
= strlen (TYPE_FIELD
) + strlen (ci
->ci_type
)
1681 + strlen (ci
->ci_subtype
) + 3;
1683 extbody
= ct
->c_type
== CT_MESSAGE
&& ct
->c_subtype
== MESSAGE_EXTERNAL
;
1684 mailbody
= extbody
&& ((struct exbody
*) ct
->c_ctparams
)->eb_body
;
1687 * Append the attribute/value pairs to
1688 * the end of the Content-Type line.
1691 if (ci
->ci_first_pm
) {
1692 char *s
= output_params(len
, ci
->ci_first_pm
, &len
, mailbody
);
1695 adios(NULL
, "Internal error: failed outputting Content-Type "
1703 * Append any RFC-822 comment to the end of
1704 * the Content-Type line.
1706 if (ci
->ci_comment
) {
1707 snprintf (buffer
, sizeof(buffer
), "(%s)", ci
->ci_comment
);
1708 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
1709 vp
= add ("\n\t", vp
);
1715 vp
= add (buffer
, vp
);
1718 vp
= add ("\n", vp
);
1719 add_header (ct
, np
, vp
);
1722 * output the Content-ID, unless disabled by -nocontentid. Note that
1723 * RFC 2045 always requires a Content-ID header for message/external-body
1726 if ((contentidsw
|| ct
->c_ctexbody
) && ct
->c_id
) {
1727 np
= mh_xstrdup(ID_FIELD
);
1728 vp
= concat (" ", ct
->c_id
, NULL
);
1729 add_header (ct
, np
, vp
);
1732 * output the Content-Description
1735 np
= mh_xstrdup(DESCR_FIELD
);
1736 vp
= concat (" ", ct
->c_descr
, NULL
);
1737 if (header_encoding
!= CE_8BIT
) {
1738 if (encode_rfc2047(DESCR_FIELD
, &vp
, header_encoding
, NULL
)) {
1739 adios(NULL
, "Unable to encode %s header", DESCR_FIELD
);
1742 add_header (ct
, np
, vp
);
1746 * output the Content-Disposition. If it's NULL but c_dispo_type is
1747 * set, then we need to build it.
1750 np
= mh_xstrdup(DISPO_FIELD
);
1751 vp
= concat (" ", ct
->c_dispo
, NULL
);
1752 add_header (ct
, np
, vp
);
1753 } else if (ct
->c_dispo_type
) {
1754 vp
= concat (" ", ct
->c_dispo_type
, NULL
);
1755 len
= strlen(DISPO_FIELD
) + strlen(vp
) + 1;
1756 np
= output_params(len
, ct
->c_dispo_first
, NULL
, 0);
1760 add_header (ct
, mh_xstrdup(DISPO_FIELD
), vp
);
1765 * If this is the internal content structure for a
1766 * "message/external", then we are done with the
1767 * headers (since it has no body).
1773 * output the Content-MD5
1776 np
= mh_xstrdup(MD5_FIELD
);
1777 vp
= calculate_digest (ct
, (ct
->c_encoding
== CE_QUOTED
) ? 1 : 0);
1778 add_header (ct
, np
, vp
);
1782 * output the Content-Transfer-Encoding
1783 * If using EAI and message body is 7-bit, force 8-bit C-T-E.
1785 if (header_encoding
== CE_8BIT
&& ct
->c_encoding
== CE_7BIT
) {
1786 ct
->c_encoding
= CE_8BIT
;
1789 switch (ct
->c_encoding
) {
1791 /* Nothing to output */
1795 np
= mh_xstrdup(ENCODING_FIELD
);
1796 vp
= concat (" ", "8bit", "\n", NULL
);
1797 add_header (ct
, np
, vp
);
1801 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1802 adios (NULL
, "internal error, invalid encoding");
1804 np
= mh_xstrdup(ENCODING_FIELD
);
1805 vp
= concat (" ", "quoted-printable", "\n", NULL
);
1806 add_header (ct
, np
, vp
);
1810 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1811 adios (NULL
, "internal error, invalid encoding");
1813 np
= mh_xstrdup(ENCODING_FIELD
);
1814 vp
= concat (" ", "base64", "\n", NULL
);
1815 add_header (ct
, np
, vp
);
1819 if (ct
->c_type
== CT_MESSAGE
)
1820 adios (NULL
, "internal error, invalid encoding");
1822 np
= mh_xstrdup(ENCODING_FIELD
);
1823 vp
= concat (" ", "binary", "\n", NULL
);
1824 add_header (ct
, np
, vp
);
1828 adios (NULL
, "unknown transfer encoding in content");
1833 * Additional content specific header processing
1835 switch (ct
->c_type
) {
1838 struct multipart
*m
;
1841 m
= (struct multipart
*) ct
->c_ctparams
;
1842 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1846 build_headers (p
, header_encoding
);
1852 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1855 e
= (struct exbody
*) ct
->c_ctparams
;
1856 build_headers (e
->eb_content
, header_encoding
);
1869 static char nib2b64
[0x40+1] =
1870 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1873 calculate_digest (CT ct
, int asciiP
)
1878 unsigned char digest
[16];
1879 unsigned char outbuf
[25];
1881 CE ce
= &ct
->c_cefile
;
1882 char *infilename
= ce
->ce_file
? ce
->ce_file
: ct
->c_file
;
1886 if ((in
= fopen (infilename
, "r")) == NULL
)
1887 adios (infilename
, "unable to open for reading");
1889 /* Initialize md5 context */
1890 MD5Init (&mdContext
);
1892 /* calculate md5 message digest */
1897 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1900 cp
= bufp
+ gotlen
- 1;
1901 if ((c
= *cp
) == '\n')
1904 MD5Update (&mdContext
, (unsigned char *) bufp
,
1905 (unsigned int) gotlen
);
1908 MD5Update (&mdContext
, (unsigned char *) "\r\n", 2);
1911 char buffer
[BUFSIZ
];
1912 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), in
)) > 0)
1913 MD5Update (&mdContext
, (unsigned char *) buffer
, (unsigned int) cc
);
1916 /* md5 finalization. Write digest and zero md5 context */
1917 MD5Final (digest
, &mdContext
);
1922 /* print debugging info */
1926 fprintf (stderr
, "MD5 digest=");
1927 for (ep
= (dp
= digest
) + sizeof(digest
) / sizeof(digest
[0]);
1929 fprintf (stderr
, "%02x", *dp
& 0xff);
1930 fprintf (stderr
, "\n");
1933 /* encode the digest using base64 */
1934 for (dp
= digest
, op
= (char *) outbuf
,
1935 cc
= sizeof(digest
) / sizeof(digest
[0]);
1936 cc
> 0; cc
-= 3, op
+= 4) {
1940 bits
= (*dp
++ & 0xff) << 16;
1942 bits
|= (*dp
++ & 0xff) << 8;
1944 bits
|= *dp
++ & 0xff;
1947 for (bp
= op
+ 4; bp
> op
; bits
>>= 6)
1948 *--bp
= nib2b64
[bits
& 0x3f];
1956 /* null terminate string */
1959 /* now make copy and return string */
1960 vp
= concat (" ", outbuf
, "\n", NULL
);
1965 * Set things up for the content structure for file "filename" that
1970 setup_attach_content(CT ct
, char *filename
)
1972 char *type
, *simplename
= r1bindex(filename
, '/');
1973 struct str2init
*s2i
;
1976 if (! (type
= mime_type(filename
))) {
1977 adios(NULL
, "Unable to determine MIME type of \"%s\"", filename
);
1981 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1982 * since we're just feeding it a MIME type we have to add those ourself.
1983 * Map that to a valid content-type label and call any initialization
1987 if (get_ctinfo(type
, ct
, 0) == NOTOK
)
1992 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
1993 if (strcasecmp(ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0)
1995 if (!s2i
->si_key
&& !uprf(ct
->c_ctinfo
.ci_type
, "X-"))
1999 * Make sure the type isn't incompatible with what we can handle
2002 switch (ct
->c_type
= s2i
->si_val
) {
2004 adios (NULL
, "multipart types must be specified by mhbuild directives");
2008 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "partial") == 0)
2009 adios(NULL
, "Sorry, %s/%s isn't supported", ct
->c_ctinfo
.ci_type
,
2010 ct
->c_ctinfo
.ci_subtype
);
2011 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "external-body") == 0)
2012 adios(NULL
, "external-body messages must be specified "
2013 "by mhbuild directives");
2018 * This sets the subtype, if it's significant
2020 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
2021 (*ct
->c_ctinitfnx
)(ct
);
2026 * Feed in a few attributes; specifically, the name attribute, the
2027 * content-description, and the content-disposition.
2030 for (pm
= ct
->c_ctinfo
.ci_first_pm
; pm
; pm
= pm
->pm_next
) {
2031 if (strcasecmp(pm
->pm_name
, "name") == 0) {
2032 mh_xfree(pm
->pm_value
);
2033 pm
->pm_value
= mh_xstrdup(simplename
);
2039 add_param(&ct
->c_ctinfo
.ci_first_pm
, &ct
->c_ctinfo
.ci_last_pm
,
2040 "name", simplename
, 0);
2042 ct
->c_descr
= mh_xstrdup(simplename
);
2043 ct
->c_descr
= add("\n", ct
->c_descr
);
2044 ct
->c_cefile
.ce_file
= mh_xstrdup(filename
);
2046 set_disposition (ct
);
2048 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename", simplename
, 0);
2052 * If disposition type hasn't already been set in ct:
2053 * Look for mhbuild-disposition-<type>/<subtype> entry
2054 * that specifies Content-Disposition type. Only
2055 * 'attachment' and 'inline' are allowed. Default to
2059 set_disposition (CT ct
) {
2060 if (ct
->c_dispo_type
== NULL
) {
2061 char *cp
= context_find_by_type ("disposition", ct
->c_ctinfo
.ci_type
,
2062 ct
->c_ctinfo
.ci_subtype
);
2064 if (cp
&& strcasecmp (cp
, "attachment") &&
2065 strcasecmp (cp
, "inline")) {
2066 inform("configuration problem: %s-disposition-%s%s%s specifies "
2067 "'%s' but only 'attachment' and 'inline' are allowed, "
2068 "continuing...", invo_name
,
2069 ct
->c_ctinfo
.ci_type
,
2070 ct
->c_ctinfo
.ci_subtype
? "/" : "",
2071 ct
->c_ctinfo
.ci_subtype
? ct
->c_ctinfo
.ci_subtype
: "",
2077 ct
->c_dispo_type
= mh_xstrdup(cp
);
2082 * Set text content charset if it was unspecified. contains8bit
2084 * 0: content does not contain 8-bit characters
2085 * 1: content contains 8-bit characters
2086 * -1: ignore content and use user's locale to determine charset
2089 set_charset (CT ct
, int contains8bit
) {
2090 if (ct
->c_type
== CT_TEXT
) {
2093 if (ct
->c_ctparams
== NULL
) {
2096 t
->tx_charset
= CHARSET_UNSPECIFIED
;
2098 t
= (struct text
*) ct
->c_ctparams
;
2101 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
2102 CI ci
= &ct
->c_ctinfo
;
2103 char *eightbitcharset
= write_charset_8bit();
2104 char *charset
= contains8bit
? eightbitcharset
: "us-ascii";
2106 if (contains8bit
== 1 &&
2107 strcasecmp (eightbitcharset
, "US-ASCII") == 0) {
2108 adios (NULL
, "Text content contains 8 bit characters, but "
2109 "character set is US-ASCII");
2112 add_param (&ci
->ci_first_pm
, &ci
->ci_last_pm
, "charset", charset
,
2115 t
->tx_charset
= CHARSET_SPECIFIED
;
2122 * Look at all of the replied-to message parts and expand any that
2123 * are matched by a pseudoheader. Except don't descend into
2127 expand_pseudoheaders (CT ct
, struct multipart
*m
, const char *infile
,
2128 const convert_list
*convert_head
) {
2129 /* text_plain_ct is used to concatenate all of the text/plain
2130 replies into one part, instead of having each one in a separate
2132 CT text_plain_ct
= NULL
;
2134 switch (ct
->c_type
) {
2135 case CT_MULTIPART
: {
2136 struct multipart
*mp
= (struct multipart
*) ct
->c_ctparams
;
2139 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
2142 /* The parts are in descending priority order (defined by
2143 RFC 2046 Sec. 5.1.4) because they were reversed by
2144 parse_mime (). So, stop looking for matches with
2145 immediate subparts after the first match of an
2147 for (part
= mp
->mp_parts
; ! matched
&& part
; part
= part
->mp_next
) {
2148 char *type_subtype
=
2149 concat (part
->mp_part
->c_ctinfo
.ci_type
, "/",
2150 part
->mp_part
->c_ctinfo
.ci_subtype
, NULL
);
2152 if (part
->mp_part
->c_type
== CT_MULTIPART
) {
2153 expand_pseudoheaders (part
->mp_part
, m
, infile
,
2156 const convert_list
*c
;
2158 for (c
= convert_head
; c
; c
= c
->next
) {
2159 if (! strcasecmp (type_subtype
, c
->type
)) {
2160 expand_pseudoheader (part
->mp_part
, &text_plain_ct
,
2162 c
->type
, c
->argstring
);
2168 free (type_subtype
);
2171 for (part
= mp
->mp_parts
; part
; part
= part
->mp_next
) {
2172 expand_pseudoheaders (part
->mp_part
, m
, infile
, convert_head
);
2179 char *type_subtype
=
2180 concat (ct
->c_ctinfo
.ci_type
, "/", ct
->c_ctinfo
.ci_subtype
,
2182 const convert_list
*c
;
2184 for (c
= convert_head
; c
; c
= c
->next
) {
2185 if (! strcasecmp (type_subtype
, c
->type
)) {
2186 expand_pseudoheader (ct
, &text_plain_ct
, m
, infile
, c
->type
,
2191 free (type_subtype
);
2199 * Expand a single pseudoheader. It's for the specified type.
2202 expand_pseudoheader (CT ct
, CT
*text_plain_ct
, struct multipart
*m
,
2203 const char *infile
, const char *type
,
2204 const char *argstring
) {
2206 FILE *reply_fp
= NULL
;
2207 char *convert
, *type_p
, *subtype_p
;
2208 char *convert_command
;
2209 char *charset
= NULL
;
2211 struct str2init
*s2i
;
2217 type_p
= getcpy (type
);
2218 if ((subtype_p
= strchr (type_p
, '/'))) {
2219 *subtype_p
++ = '\0';
2220 convert
= context_find_by_type ("convert", type_p
, subtype_p
);
2223 type_p
= concat ("mhbuild-convert-", type
, NULL
);
2224 convert
= context_find (type_p
);
2229 /* No mhbuild-convert- entry in mhn.defaults or profile
2233 /* reply_file is used to pass the output of the convert. */
2234 reply_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2236 concat (convert
, " ", argstring
? argstring
: "", " >", reply_file
,
2239 /* Convert here . . . */
2240 ct
->c_storeproc
= mh_xstrdup(convert_command
);
2241 ct
->c_umask
= ~m_gmprot ();
2243 if ((status
= show_content_aux (ct
, 0, convert_command
, NULL
, NULL
)) !=
2245 inform("store of %s content failed, continuing...", type
);
2247 free (convert_command
);
2249 /* Fill out the the new ct, reply_ct. */
2251 init_decoded_content (reply_ct
, infile
);
2253 if (extract_headers (reply_ct
, reply_file
, &reply_fp
) == NOTOK
) {
2255 inform("failed to extract headers from convert output in %s, "
2256 "continuing...", reply_file
);
2260 /* For text content only, see if it is 8-bit text. */
2261 if (reply_ct
->c_type
== CT_TEXT
) {
2264 if ((fd
= open (reply_file
, O_RDONLY
)) == NOTOK
||
2265 scan_input (fd
, &eightbit
) == NOTOK
) {
2267 inform("failed to read %s, continuing...", reply_file
);
2273 /* This sets reply_ct->c_ctparams, and reply_ct->c_termproc if the
2274 charset can't be handled natively. */
2275 for (s2i
= str2cts
; s2i
->si_key
; s2i
++) {
2276 if (strcasecmp(reply_ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0) {
2281 if ((reply_ct
->c_ctinitfnx
= s2i
->si_init
)) {
2282 (*reply_ct
->c_ctinitfnx
)(reply_ct
);
2286 get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1))) {
2287 /* The reply Content-Type had the charset. */
2290 set_charset (reply_ct
, -1);
2291 charset
= get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1);
2292 if (reply_ct
->c_reqencoding
== CE_UNKNOWN
&&
2293 reply_ct
->c_type
== CT_TEXT
) {
2294 /* Assume that 8bit is sufficient (for text). In other words,
2295 don't allow it to be encoded as quoted printable if lines
2296 are too long. This also sidesteps the check for whether
2297 it needs to be encoded as binary; instead, it relies on
2298 the applicable mhbuild-convert-text directive to ensure
2299 that the resultant text is not binary. */
2300 reply_ct
->c_reqencoding
= eightbit
? CE_8BIT
: CE_7BIT
;
2304 /* Concatenate text/plain parts. */
2305 if (reply_ct
->c_type
== CT_TEXT
&&
2306 reply_ct
->c_subtype
== TEXT_PLAIN
) {
2307 if (! *text_plain_ct
&& m
->mp_parts
&& m
->mp_parts
->mp_part
&&
2308 m
->mp_parts
->mp_part
->c_type
== CT_TEXT
&&
2309 m
->mp_parts
->mp_part
->c_subtype
== TEXT_PLAIN
) {
2310 *text_plain_ct
= m
->mp_parts
->mp_part
;
2311 /* Make sure that the charset is set in the text/plain
2313 set_charset (*text_plain_ct
, -1);
2314 if ((*text_plain_ct
)->c_reqencoding
== CE_UNKNOWN
) {
2315 /* Assume that 8bit is sufficient (for text). In other words,
2316 don't allow it to be encoded as quoted printable if lines
2317 are too long. This also sidesteps the check for whether
2318 it needs to be encoded as binary; instead, it relies on
2319 the applicable mhbuild-convert-text directive to ensure
2320 that the resultant text is not binary. */
2321 (*text_plain_ct
)->c_reqencoding
=
2322 eightbit
? CE_8BIT
: CE_7BIT
;
2326 if (*text_plain_ct
) {
2327 /* Only concatenate if the charsets are identical. */
2328 char *text_plain_ct_charset
=
2329 get_param ((*text_plain_ct
)->c_ctinfo
.ci_first_pm
, "charset",
2332 if (strcasecmp (text_plain_ct_charset
, charset
) == 0) {
2333 /* Append this text/plain reply to the first one.
2334 If there's a problem anywhere along the way,
2335 instead attach it is a separate part. */
2336 int text_plain_reply
=
2337 open ((*text_plain_ct
)->c_cefile
.ce_file
,
2338 O_WRONLY
| O_APPEND
);
2339 int addl_reply
= open (reply_file
, O_RDONLY
);
2341 if (text_plain_reply
!= NOTOK
&& addl_reply
!= NOTOK
) {
2342 /* Insert blank line before each addl part. */
2343 /* It would be nice not to do this for the first one. */
2344 if (write (text_plain_reply
, "\n", 1) == 1) {
2345 /* Copy the text from the new reply and
2346 then free its Content struct. */
2347 cpydata (addl_reply
, text_plain_reply
,
2348 (*text_plain_ct
)->c_cefile
.ce_file
,
2350 if (close (text_plain_reply
) == OK
&&
2351 close (addl_reply
) == OK
) {
2352 /* If appended text needed 8-bit but first text didn't,
2353 propagate the 8-bit indication. */
2354 if ((*text_plain_ct
)->c_reqencoding
== CE_7BIT
&&
2355 reply_ct
->c_reqencoding
== CE_8BIT
) {
2356 (*text_plain_ct
)->c_reqencoding
= CE_8BIT
;
2359 if (reply_fp
) { fclose (reply_fp
); }
2361 free_content (reply_ct
);
2368 *text_plain_ct
= reply_ct
;
2372 reply_ct
->c_cefile
.ce_file
= reply_file
;
2373 reply_ct
->c_cefile
.ce_fp
= reply_fp
;
2374 reply_ct
->c_cefile
.ce_unlink
= 1;
2376 /* Attach the new part to the parent multipart/mixed, "m". */
2378 part
->mp_part
= reply_ct
;
2382 for (p
= m
->mp_parts
; p
&& p
->mp_next
; p
= p
->mp_next
) { continue; }
2390 /* Extract any Content-Type header from beginning of convert output. */
2392 extract_headers (CT ct
, char *reply_file
, FILE **reply_fp
) {
2393 char *buffer
= NULL
, *cp
, *end_of_header
;
2394 int found_header
= 0;
2395 struct stat statbuf
;
2397 /* Read the convert reply from the file to memory. */
2398 if (stat (reply_file
, &statbuf
) == NOTOK
) {
2399 admonish (reply_file
, "failed to stat");
2400 goto failed_to_extract_ct
;
2403 buffer
= mh_xmalloc (statbuf
.st_size
+ 1);
2405 if ((*reply_fp
= fopen (reply_file
, "r+")) == NULL
||
2406 fread (buffer
, 1, (size_t) statbuf
.st_size
, *reply_fp
) <
2407 (size_t) statbuf
.st_size
) {
2408 admonish (reply_file
, "failed to read");
2409 goto failed_to_extract_ct
;
2411 buffer
[statbuf
.st_size
] = '\0';
2413 /* Look for a header in the convert reply. */
2414 if (strncasecmp (buffer
, TYPE_FIELD
, strlen (TYPE_FIELD
)) == 0 &&
2415 buffer
[strlen (TYPE_FIELD
)] == ':') {
2416 if ((end_of_header
= strstr (buffer
, "\r\n\r\n"))) {
2419 } else if ((end_of_header
= strstr (buffer
, "\n\n"))) {
2431 /* Truncate buffer to just the C-T. */
2432 *end_of_header
= '\0';
2433 n
= strlen (buffer
);
2435 if (get_ctinfo (buffer
+ 14, ct
, 0) != OK
) {
2436 inform("unable to get content info for reply, continuing...");
2437 goto failed_to_extract_ct
;
2440 /* Hack. Use parse_mime() to detect the type/subtype of the
2441 reply, which we'll use below. */
2442 tmp_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2443 if ((tmp_f
= fopen (tmp_file
, "w")) &&
2444 fwrite (buffer
, 1, n
, tmp_f
) == n
) {
2447 goto failed_to_extract_ct
;
2449 tmp_ct
= parse_mime (tmp_file
);
2452 /* The type and subtype were detected from the reply
2453 using parse_mime() above. */
2454 ct
->c_type
= tmp_ct
->c_type
;
2455 ct
->c_subtype
= tmp_ct
->c_subtype
;
2456 free_content (tmp_ct
);
2461 /* Rewrite the content without the header. */
2462 cp
= end_of_header
+ 1;
2465 if (fwrite (cp
, 1, statbuf
.st_size
- (cp
- buffer
), *reply_fp
) <
2466 (size_t) (statbuf
.st_size
- (cp
- buffer
))) {
2467 admonish (reply_file
, "failed to write");
2468 goto failed_to_extract_ct
;
2471 if (ftruncate (fileno (*reply_fp
), statbuf
.st_size
- (cp
- buffer
)) !=
2473 advise (reply_file
, "ftruncate");
2474 goto failed_to_extract_ct
;
2477 /* No header section, assume the reply is text/plain. */
2478 ct
->c_type
= CT_TEXT
;
2479 ct
->c_subtype
= TEXT_PLAIN
;
2480 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
) {
2481 /* This never should fail, but just in case. */
2482 adios (NULL
, "unable to get content info for reply");
2486 /* free_encoding() will close reply_fp, which is passed through
2487 ct->c_cefile.ce_fp. */
2491 failed_to_extract_ct
:
2492 if (*reply_fp
) { fclose (*reply_fp
); }