3 * mhbuildsbr.c -- routines to expand/translate MIME composition files
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
11 * This code was originally part of mhn.c. I split it into
12 * a separate program (mhbuild.c) and then later split it
13 * again (mhbuildsbr.c). But the code still has some of
14 * the mhn.c code in it. This program needs additional
15 * streamlining and removal of unneeded code.
24 #include <h/mhparse.h>
27 #ifdef HAVE_SYS_TIME_H
28 # include <sys/time.h>
37 extern int contentidsw
;
40 extern int rcachesw
; /* mhcachesbr.c */
41 extern int wcachesw
; /* mhcachesbr.c */
43 static char prefix
[] = "----- =_aaaaaaaaaa";
47 struct attach_list
*next
;
50 typedef struct convert_list
{
54 struct convert_list
*next
;
58 * Maximum size of URL token in message/external-body
61 #define MAXURLTOKEN 40
65 void content_error (char *, CT
, char *, ...);
68 int find_cache (CT
, int, int *, char *, char *, int);
72 void freects_done (int) NORETURN
;
73 void free_ctinfo (CT
);
74 void free_encoding (CT
, int);
79 static int init_decoded_content (CT
, const char *);
80 static void setup_attach_content(CT
, char *);
81 static void set_disposition (CT
);
82 static void set_charset (CT
, int);
83 static void expand_pseudoheaders (CT
, struct multipart
*, const char *,
84 const convert_list
*);
85 static void expand_pseudoheader (CT
, CT
*, struct multipart
*, const char *,
86 const char *, const char *);
87 static char *fgetstr (char *, int, FILE *);
88 static int user_content (FILE *, char *, CT
*, const char *infilename
);
89 static void set_id (CT
, int);
90 static int compose_content (CT
, int);
91 static int scan_content (CT
, size_t);
92 static int build_headers (CT
, int);
93 static char *calculate_digest (CT
, int);
94 static int extract_headers (CT
, char *, FILE **);
97 static unsigned char directives_stack
[32];
98 static unsigned int directives_index
;
100 static int do_direct(void)
102 return directives_stack
[directives_index
];
105 static void directive_onoff(int onoff
)
107 if (directives_index
>= sizeof(directives_stack
) - 1) {
108 fprintf(stderr
, "mhbuild: #on/off overflow, continuing\n");
111 directives_stack
[++directives_index
] = onoff
;
114 static void directive_init(int onoff
)
116 directives_index
= 0;
117 directives_stack
[0] = onoff
;
120 static void directive_pop(void)
122 if (directives_index
> 0)
125 fprintf(stderr
, "mhbuild: #pop underflow, continuing\n");
129 * Main routine for translating composition file
130 * into valid MIME message. It translates the draft
131 * into a content structure (actually a tree of content
132 * structures). This message then can be manipulated
133 * in various ways, including being output via
138 build_mime (char *infile
, int autobuild
, int dist
, int directives
,
139 int header_encoding
, size_t maxunencoded
, int verbose
)
142 char buf
[BUFSIZ
], name
[NAMESZ
];
149 m_getfld_state_t gstate
= 0;
150 struct attach_list
*attach_head
= NULL
, *attach_tail
= NULL
, *at_entry
;
151 convert_list
*convert_head
= NULL
, *convert_tail
= NULL
, *convert
;
153 directive_init(directives
);
155 umask (~m_gmprot ());
157 /* open the composition draft */
158 if ((in
= fopen (infile
, "r")) == NULL
)
159 adios (infile
, "unable to open for reading");
162 * Allocate space for primary (outside) content
164 if ((ct
= (CT
) mh_xcalloc (1, sizeof(*ct
))) == NULL
)
165 adios (NULL
, "out of memory");
168 * Allocate structure for handling decoded content
169 * for this part. We don't really need this, but
170 * allocate it to remain consistent.
172 init_decoded_content (ct
, infile
);
175 * Parse some of the header fields in the composition
176 * draft into the linked list of header fields for
177 * the new MIME message.
179 m_getfld_track_filepos (&gstate
, in
);
180 for (compnum
= 1;;) {
181 int bufsz
= sizeof buf
;
182 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
187 /* abort if draft has Mime-Version or C-T-E header field */
188 if (strcasecmp (name
, VRSN_FIELD
) == 0 ||
189 strcasecmp (name
, ENCODING_FIELD
) == 0) {
195 adios (NULL
, "draft shouldn't contain %s: field", name
);
199 /* ignore any Content-Type fields in the header */
200 if (!strcasecmp (name
, TYPE_FIELD
)) {
201 while (state
== FLDPLUS
) {
203 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
208 /* get copies of the buffers */
209 np
= add (name
, NULL
);
210 vp
= add (buf
, NULL
);
212 /* if necessary, get rest of field */
213 while (state
== FLDPLUS
) {
215 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
216 vp
= add (buf
, vp
); /* add to previous value */
220 * Now add the header data to the list, unless it's an attach
221 * header; in that case, add it to our attach list
224 if (strcasecmp(ATTACH_FIELD
, np
) == 0 ||
225 strcasecmp(ATTACH_FIELD_ALT
, np
) == 0) {
226 struct attach_list
*entry
;
227 char *s
= vp
, *e
= vp
+ strlen(vp
) - 1;
231 * Make sure we can find the start of this filename.
232 * If it's blank, we skip completely. Otherwise, strip
233 * off any leading spaces and trailing newlines.
236 while (isspace((unsigned char) *s
))
239 while (e
> s
&& *e
== '\n')
247 entry
= mh_xmalloc(sizeof(*entry
));
248 entry
->filename
= getcpy(s
);
253 attach_tail
->next
= entry
;
256 attach_head
= attach_tail
= entry
;
258 } else if (strncasecmp(MHBUILD_FILE_PSEUDOHEADER
, np
,
259 strlen (MHBUILD_FILE_PSEUDOHEADER
)) == 0) {
261 * Nmh-mhbuild-file-text/calendar: /home/user/Mail/inbox/9
263 char *type
= np
+ strlen (MHBUILD_FILE_PSEUDOHEADER
);
266 /* vp should begin with a space because m_getfld()
267 includes the space after the colon in buf. */
268 while (isspace((unsigned char) *filename
)) { ++filename
; }
269 /* Trim trailing newline and any other whitespace. */
272 for (convert
= convert_head
; convert
; convert
= convert
->next
) {
273 if (strcasecmp (convert
->type
, type
) == 0) { break; }
276 if (convert
->filename
&&
277 strcasecmp (convert
->filename
, filename
)) {
278 adios (NULL
, "Multiple %s headers with different files"
279 " not allowed", type
);
281 convert
->filename
= getcpy (filename
);
284 convert
= mh_xcalloc (sizeof *convert
, 1);
285 convert
->filename
= getcpy (filename
);
286 convert
->type
= getcpy (type
);
289 convert_tail
->next
= convert
;
291 convert_head
= convert
;
293 convert_tail
= convert
;
298 } else if (strncasecmp(MHBUILD_ARGS_PSEUDOHEADER
, np
,
299 strlen (MHBUILD_ARGS_PSEUDOHEADER
)) == 0) {
301 * Nmh-mhbuild-args-text/calendar: -reply accept
303 char *type
= np
+ strlen (MHBUILD_ARGS_PSEUDOHEADER
);
304 char *argstring
= vp
;
306 /* vp should begin with a space because m_getfld()
307 includes the space after the colon in buf. */
308 while (isspace((unsigned char) *argstring
)) { ++argstring
; }
309 /* Trim trailing newline and any other whitespace. */
312 for (convert
= convert_head
; convert
; convert
= convert
->next
) {
313 if (strcasecmp (convert
->type
, type
) == 0) { break; }
316 if (convert
->argstring
&&
317 strcasecmp (convert
->argstring
, argstring
)) {
318 adios (NULL
, "Multiple %s headers with different "
319 "argstrings not allowed", type
);
321 convert
->argstring
= getcpy (argstring
);
324 convert
= mh_xcalloc (sizeof *convert
, 1);
325 convert
->type
= getcpy (type
);
326 convert
->argstring
= getcpy (argstring
);
329 convert_tail
->next
= convert
;
331 convert_head
= convert
;
333 convert_tail
= convert
;
339 add_header (ct
, np
, vp
);
343 /* if this wasn't the last header field, then continue */
347 fseek (in
, (long) (-strlen (buf
)), SEEK_CUR
);
354 adios (NULL
, "message format error in component #%d", compnum
);
357 adios (NULL
, "getfld() returned %d", state
);
361 m_getfld_state_destroy (&gstate
);
363 if (header_encoding
!= CE_8BIT
) {
365 * Iterate through the list of headers and call the function to MIME-ify
369 for (hp
= ct
->c_first_hf
; hp
!= NULL
; hp
= hp
->next
) {
370 if (encode_rfc2047(hp
->name
, &hp
->value
, header_encoding
, NULL
)) {
371 adios(NULL
, "Unable to encode header \"%s\"", hp
->name
);
377 * Now add the MIME-Version header field
378 * to the list of header fields.
382 np
= add (VRSN_FIELD
, NULL
);
383 vp
= concat (" ", VRSN_VALUE
, "\n", NULL
);
384 add_header (ct
, np
, vp
);
388 * We initally assume we will find multiple contents in the
389 * draft. So create a multipart/mixed content to hold everything.
390 * We can remove this later, if it is not needed.
392 if (get_ctinfo ("multipart/mixed", ct
, 0) == NOTOK
)
394 ct
->c_type
= CT_MULTIPART
;
395 ct
->c_subtype
= MULTI_MIXED
;
397 if ((m
= (struct multipart
*) mh_xcalloc (1, sizeof(*m
))) == NULL
)
398 adios (NULL
, "out of memory");
399 ct
->c_ctparams
= (void *) m
;
403 * read and parse the composition file
404 * and the directives it contains.
406 while (fgetstr (buf
, sizeof(buf
) - 1, in
)) {
410 if (user_content (in
, buf
, &p
, infile
) == DONE
) {
411 admonish (NULL
, "ignoring spurious #end");
417 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
418 adios (NULL
, "out of memory");
425 * Add any Attach headers to the list of MIME parts at the end of the
429 for (at_entry
= attach_head
; at_entry
; ) {
430 struct attach_list
*at_prev
= at_entry
;
434 if (access(at_entry
->filename
, R_OK
) != 0) {
435 adios("reading", "Unable to open %s for", at_entry
->filename
);
438 if ((p
= (CT
) mh_xcalloc (1, sizeof(*p
))) == NULL
)
439 adios(NULL
, "out of memory");
441 init_decoded_content(p
, infile
);
444 * Initialize our content structure based on the filename,
445 * and fill in all of the relevant fields. Also place MIME
446 * parameters in the attributes array.
449 setup_attach_content(p
, at_entry
->filename
);
451 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
452 adios (NULL
, "out of memory");
457 at_entry
= at_entry
->next
;
458 free(at_prev
->filename
);
463 * Handle the mhbuild pseudoheaders, which deal with specific
472 /* In case there are multiple calls that land here, prevent leak. */
473 for (ctp
= cts
; ctp
&& *ctp
; ++ctp
) { free_content (*ctp
); }
476 /* Extract the type part (as a CT) from filename. */
477 if (! (cts
= (CT
*) mh_xcalloc ((size_t) 2, sizeof *cts
))) {
478 adios (NULL
, "out of memory");
479 } else if (! (cts
[0] = parse_mime (convert_head
->filename
))) {
480 adios (NULL
, "failed to parse %s", convert_head
->filename
);
483 expand_pseudoheaders (cts
[0], m
, infile
, convert_head
);
485 /* Free the convert list. */
486 for (convert
= convert_head
; convert
; convert
= next
) {
487 next
= convert
->next
;
488 free (convert
->type
);
489 free (convert
->filename
);
490 free (convert
->argstring
);
497 * To allow for empty message bodies, if we've found NO content at all
498 * yet cook up an empty text/plain part.
506 if ((p
= (CT
) mh_xcalloc (1, sizeof(*p
))) == NULL
)
507 adios(NULL
, "out of memory");
509 init_decoded_content(p
, infile
);
511 if (get_ctinfo ("text/plain", p
, 0) == NOTOK
)
515 p
->c_subtype
= TEXT_PLAIN
;
516 p
->c_encoding
= CE_7BIT
;
518 * Sigh. ce_file contains the "decoded" contents of this part.
519 * So this seems like the best option available since we're going
520 * to call scan_content() on this.
522 p
->c_cefile
.ce_file
= getcpy("/dev/null");
523 p
->c_begin
= ftell(in
);
524 p
->c_end
= ftell(in
);
526 if ((t
= (struct text
*) mh_xcalloc (1, sizeof (*t
))) == NULL
)
527 adios (NULL
, "out of memory");
529 t
->tx_charset
= CHARSET_SPECIFIED
;
532 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
533 adios (NULL
, "out of memory");
539 * close the composition draft since
540 * it's not needed any longer.
545 * If only one content was found, then remove and
546 * free the outer multipart content.
548 if (!m
->mp_parts
->mp_next
) {
551 p
= m
->mp_parts
->mp_part
;
552 m
->mp_parts
->mp_part
= NULL
;
554 /* move header fields */
555 p
->c_first_hf
= ct
->c_first_hf
;
556 p
->c_last_hf
= ct
->c_last_hf
;
557 ct
->c_first_hf
= NULL
;
558 ct
->c_last_hf
= NULL
;
567 * Fill out, or expand directives. Parse and execute
568 * commands specified by profile composition strings.
570 compose_content (ct
, verbose
);
572 if ((cp
= strchr(prefix
, 'a')) == NULL
)
573 adios (NULL
, "internal error(4)");
576 * Scan the contents. Choose a transfer encoding, and
577 * check if prefix for multipart boundary clashes with
578 * any of the contents.
580 while (scan_content (ct
, maxunencoded
) == NOTOK
) {
585 adios (NULL
, "giving up trying to find a unique delimiter string");
591 /* Build the rest of the header field structures */
593 build_headers (ct
, header_encoding
);
600 * Set up structures for placing unencoded
601 * content when building parts.
605 init_decoded_content (CT ct
, const char *filename
)
607 ct
->c_ceopenfnx
= open7Bit
; /* since unencoded */
608 ct
->c_ceclosefnx
= close_encoding
;
609 ct
->c_cesizefnx
= NULL
; /* since unencoded */
610 ct
->c_encoding
= CE_7BIT
; /* Seems like a reasonable default */
611 ct
->c_file
= add(filename
, NULL
);
618 fgetstr (char *s
, int n
, FILE *stream
)
624 for (ep
= (cp
= s
) + o_n
; cp
< ep
; ) {
627 if (!fgets (cp
, n
, stream
))
628 return (cp
!= s
? s
: NULL
);
630 if (cp
== s
&& *cp
!= '#')
633 cp
+= (i
= strlen (cp
)) - 1;
634 if (i
<= 1 || *cp
-- != '\n' || *cp
!= '\\')
640 if (strcmp(s
, "#on\n") == 0) {
642 } else if (strcmp(s
, "#off\n") == 0) {
644 } else if (strcmp(s
, "#pop\n") == 0) {
656 * Parse the composition draft for text and directives.
657 * Do initial setup of Content structure.
661 user_content (FILE *in
, char *buf
, CT
*ctp
, const char *infilename
)
669 struct str2init
*s2i
;
674 if (buf
[0] == '\n' || (do_direct() && strcmp (buf
, "#\n") == 0)) {
679 /* allocate basic Content structure */
680 if ((ct
= (CT
) mh_xcalloc (1, sizeof(*ct
))) == NULL
)
681 adios (NULL
, "out of memory");
684 /* allocate basic structure for handling decoded content */
685 init_decoded_content (ct
, infilename
);
692 * Handle inline text. Check if line
693 * is one of the following forms:
695 * 1) doesn't begin with '#' (implicit directive)
696 * 2) begins with "##" (implicit directive)
697 * 3) begins with "#<"
699 if (!do_direct() || buf
[0] != '#' || buf
[1] == '#' || buf
[1] == '<') {
703 char content
[BUFSIZ
];
707 if ((cp
= m_mktemp2(NULL
, invo_name
, NULL
, &out
)) == NULL
) {
708 adios("mhbuildsbr", "unable to create temporary file in %s",
712 /* use a temp file to collect the plain text lines */
713 ce
->ce_file
= add (cp
, NULL
);
716 if (do_direct() && (buf
[0] == '#' && buf
[1] == '<')) {
717 strncpy (content
, buf
+ 2, sizeof(content
));
724 /* the directive is implicit */
725 strncpy (content
, "text/plain", sizeof(content
));
727 strncpy (buffer
, (!do_direct() || buf
[0] != '#') ? buf
: buf
+ 1, sizeof(buffer
));
731 if (headers
>= 0 && do_direct() && uprf (buffer
, DESCR_FIELD
)
732 && buffer
[i
= strlen (DESCR_FIELD
)] == ':') {
736 ct
->c_descr
= add (buffer
+ i
+ 1, ct
->c_descr
);
737 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
738 adios (NULL
, "end-of-file after %s: field in plaintext", DESCR_FIELD
);
746 adios (NULL
, "#-directive after %s: field in plaintext", DESCR_FIELD
);
754 if (headers
>= 0 && do_direct() && uprf (buffer
, DISPO_FIELD
)
755 && buffer
[i
= strlen (DISPO_FIELD
)] == ':') {
759 ct
->c_dispo
= add (buffer
+ i
+ 1, ct
->c_dispo
);
760 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
761 adios (NULL
, "end-of-file after %s: field in plaintext", DISPO_FIELD
);
769 adios (NULL
, "#-directive after %s: field in plaintext", DISPO_FIELD
);
777 if (headers
!= 1 || buffer
[0] != '\n')
783 if ((cp
= fgetstr (buffer
, sizeof(buffer
) - 1, in
)) == NULL
)
785 if (do_direct() && buffer
[0] == '#') {
788 if (buffer
[1] != '#')
790 for (cp
= (bp
= buffer
) + 1; *cp
; cp
++)
797 ct
->c_end
= ftell (out
);
800 /* parse content type */
801 if (get_ctinfo (content
, ct
, inlineD
) == NOTOK
)
804 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
805 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
807 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
811 * check type specified (possibly implicitly)
813 switch (ct
->c_type
= s2i
->si_val
) {
815 if (!strcasecmp (ci
->ci_subtype
, "rfc822")) {
816 ct
->c_encoding
= CE_7BIT
;
821 adios (NULL
, "it doesn't make sense to define an in-line %s content",
822 ct
->c_type
== CT_MESSAGE
? "message" : "multipart");
827 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
828 (*ct
->c_ctinitfnx
) (ct
);
833 fseek (in
, pos
, SEEK_SET
);
838 * If we've reached this point, the next line
839 * must be some type of explicit directive.
842 /* check if directive is external-type */
843 extrnal
= (buf
[1] == '@');
845 /* parse directive */
846 if (get_ctinfo (buf
+ (extrnal
? 2 : 1), ct
, 1) == NOTOK
)
849 /* check directive against the list of MIME types */
850 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
851 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
855 * Check if the directive specified a valid type.
856 * This will happen if it was one of the following forms:
863 adios (NULL
, "missing subtype in \"#%s\"", ci
->ci_type
);
865 switch (ct
->c_type
= s2i
->si_val
) {
867 adios (NULL
, "use \"#begin ... #end\" instead of \"#%s/%s\"",
868 ci
->ci_type
, ci
->ci_subtype
);
872 if (!strcasecmp (ci
->ci_subtype
, "partial"))
873 adios (NULL
, "sorry, \"#%s/%s\" isn't supported",
874 ci
->ci_type
, ci
->ci_subtype
);
875 if (!strcasecmp (ci
->ci_subtype
, "external-body"))
876 adios (NULL
, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
877 ci
->ci_type
, ci
->ci_subtype
);
880 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
881 ci
->ci_type
, ci
->ci_subtype
);
885 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
886 (*ct
->c_ctinitfnx
) (ct
);
891 * #@type/subtype (external types directive)
898 adios (NULL
, "need external information for \"#@%s/%s\"",
899 ci
->ci_type
, ci
->ci_subtype
);
902 snprintf (buffer
, sizeof(buffer
), "message/external-body; %s", ci
->ci_magic
);
907 * Since we are using the current Content structure to
908 * hold information about the type of the external
909 * reference, we need to create another Content structure
910 * for the message/external-body to wrap it in.
912 if ((ct
= (CT
) mh_xcalloc (1, sizeof(*ct
))) == NULL
)
913 adios (NULL
, "out of memory");
914 init_decoded_content(ct
, infilename
);
916 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
918 ct
->c_type
= CT_MESSAGE
;
919 ct
->c_subtype
= MESSAGE_EXTERNAL
;
921 if ((e
= (struct exbody
*) mh_xcalloc (1, sizeof(*e
))) == NULL
)
922 adios (NULL
, "out of memory");
923 ct
->c_ctparams
= (void *) e
;
929 if (params_external (ct
, 1) == NOTOK
)
935 /* Handle [file] argument */
937 /* check if specifies command to execute */
938 if (*ci
->ci_magic
== '|' || *ci
->ci_magic
== '!') {
939 for (cp
= ci
->ci_magic
+ 1; isspace ((unsigned char) *cp
); cp
++)
942 adios (NULL
, "empty pipe command for #%s directive", ci
->ci_type
);
947 /* record filename of decoded contents */
948 ce
->ce_file
= ci
->ci_magic
;
949 if (access (ce
->ce_file
, R_OK
) == NOTOK
)
950 adios ("reading", "unable to access %s for", ce
->ce_file
);
951 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
952 ct
->c_end
= (long) st
.st_size
;
959 * No [file] argument, so check profile for
960 * method to compose content.
962 cp
= context_find_by_type ("compose", ci
->ci_type
, ci
->ci_subtype
);
964 content_error (NULL
, ct
, "don't know how to compose content");
967 ci
->ci_magic
= add (cp
, NULL
);
972 adios (NULL
, "external definition not allowed for \"#%s\"", ci
->ci_type
);
976 * #forw [+folder] [msgs]
978 if (!strcasecmp (ci
->ci_type
, "forw")) {
980 char *folder
, *arguments
[MAXARGS
];
984 ap
= brkstring (ci
->ci_magic
, " ", "\n");
985 copyip (ap
, arguments
, MAXARGS
);
987 arguments
[0] = "cur";
992 /* search the arguments for a folder name */
993 for (ap
= arguments
; *ap
; ap
++) {
995 if (*cp
== '+' || *cp
== '@') {
997 adios (NULL
, "only one folder per #forw directive");
999 folder
= pluspath (cp
);
1003 /* else, use the current folder */
1005 folder
= add (getfolder (1), NULL
);
1007 if (!(mp
= folder_read (folder
, 0)))
1008 adios (NULL
, "unable to read folder %s", folder
);
1009 for (ap
= arguments
; *ap
; ap
++) {
1011 if (*cp
!= '+' && *cp
!= '@')
1012 if (!m_convert (mp
, cp
))
1019 * If there is more than one message to include, make this
1020 * a content of type "multipart/digest" and insert each message
1021 * as a subpart. If there is only one message, then make this
1022 * a content of type "message/rfc822".
1024 if (mp
->numsel
> 1) {
1025 /* we are forwarding multiple messages */
1026 if (get_ctinfo ("multipart/digest", ct
, 0) == NOTOK
)
1028 ct
->c_type
= CT_MULTIPART
;
1029 ct
->c_subtype
= MULTI_DIGEST
;
1031 if ((m
= (struct multipart
*) mh_xcalloc (1, sizeof(*m
))) == NULL
)
1032 adios (NULL
, "out of memory");
1033 ct
->c_ctparams
= (void *) m
;
1036 for (msgnum
= mp
->lowsel
; msgnum
<= mp
->hghsel
; msgnum
++) {
1037 if (is_selected(mp
, msgnum
)) {
1042 if ((p
= (CT
) mh_xcalloc (1, sizeof(*p
))) == NULL
)
1043 adios (NULL
, "out of memory");
1044 init_decoded_content (p
, infilename
);
1046 if (get_ctinfo ("message/rfc822", p
, 0) == NOTOK
)
1048 p
->c_type
= CT_MESSAGE
;
1049 p
->c_subtype
= MESSAGE_RFC822
;
1051 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1052 pe
->ce_file
= add (buffer
, NULL
);
1053 if (listsw
&& stat (pe
->ce_file
, &st
) != NOTOK
)
1054 p
->c_end
= (long) st
.st_size
;
1056 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
1057 adios (NULL
, "out of memory");
1059 pp
= &part
->mp_next
;
1064 /* we are forwarding one message */
1065 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
1067 ct
->c_type
= CT_MESSAGE
;
1068 ct
->c_subtype
= MESSAGE_RFC822
;
1070 msgnum
= mp
->lowsel
;
1071 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1072 ce
->ce_file
= add (buffer
, NULL
);
1073 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
1074 ct
->c_end
= (long) st
.st_size
;
1077 folder_free (mp
); /* free folder/message structure */
1084 if (!strcasecmp (ci
->ci_type
, "end")) {
1091 * #begin [ alternative | parallel ]
1093 if (!strcasecmp (ci
->ci_type
, "begin")) {
1094 if (!ci
->ci_magic
) {
1096 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1097 } else if (!strcasecmp (ci
->ci_magic
, "alternative")) {
1098 vrsn
= MULTI_ALTERNATE
;
1099 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1100 } else if (!strcasecmp (ci
->ci_magic
, "parallel")) {
1101 vrsn
= MULTI_PARALLEL
;
1102 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1103 } else if (uprf (ci
->ci_magic
, "digest")) {
1106 vrsn
= MULTI_UNKNOWN
;
1111 snprintf (buffer
, sizeof(buffer
), "multipart/%s", cp
);
1112 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
1114 ct
->c_type
= CT_MULTIPART
;
1115 ct
->c_subtype
= vrsn
;
1117 if ((m
= (struct multipart
*) mh_xcalloc (1, sizeof(*m
))) == NULL
)
1118 adios (NULL
, "out of memory");
1119 ct
->c_ctparams
= (void *) m
;
1122 while (fgetstr (buffer
, sizeof(buffer
) - 1, in
)) {
1126 if (user_content (in
, buffer
, &p
, infilename
) == DONE
) {
1128 adios (NULL
, "empty \"#begin ... #end\" sequence");
1134 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
1135 adios (NULL
, "out of memory");
1137 pp
= &part
->mp_next
;
1140 admonish (NULL
, "premature end-of-file, missing #end");
1147 adios (NULL
, "unknown directive \"#%s\"", ci
->ci_type
);
1148 return NOTOK
; /* NOT REACHED */
1153 set_id (CT ct
, int top
)
1155 char contentid
[BUFSIZ
];
1157 static time_t clock
= 0;
1158 static char *msgfmt
;
1162 snprintf (contentid
, sizeof(contentid
), "%s\n", message_id (clock
, 1));
1164 msgfmt
= getcpy(contentid
);
1166 snprintf (contentid
, sizeof(contentid
), msgfmt
, top
? 0 : ++partno
);
1167 ct
->c_id
= getcpy (contentid
);
1172 * Fill out, or expand the various contents in the composition
1173 * draft. Read-in any necessary files. Parse and execute any
1174 * commands specified by profile composition strings.
1178 compose_content (CT ct
, int verbose
)
1180 CE ce
= &ct
->c_cefile
;
1182 switch (ct
->c_type
) {
1187 char partnam
[BUFSIZ
];
1188 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1192 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1193 pp
= partnam
+ strlen (partnam
);
1198 /* first, we call compose_content on all the subparts */
1199 for (part
= m
->mp_parts
, partnum
= 1; part
; part
= part
->mp_next
, partnum
++) {
1200 CT p
= part
->mp_part
;
1202 sprintf (pp
, "%d", partnum
);
1203 p
->c_partno
= add (partnam
, NULL
);
1204 if (compose_content (p
, verbose
) == NOTOK
)
1209 * If the -rfc934mode switch is given, then check all
1210 * the subparts of a multipart/digest. If they are all
1211 * message/rfc822, then mark this content and all
1212 * subparts with the rfc934 compatibility mode flag.
1214 if (rfc934sw
&& ct
->c_subtype
== MULTI_DIGEST
) {
1217 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1218 CT p
= part
->mp_part
;
1220 if (p
->c_subtype
!= MESSAGE_RFC822
) {
1225 ct
->c_rfc934
= is934
;
1226 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1227 CT p
= part
->mp_part
;
1229 if ((p
->c_rfc934
= is934
))
1235 ct
->c_end
= (partnum
= strlen (prefix
) + 2) + 2;
1239 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
)
1240 ct
->c_end
+= part
->mp_part
->c_end
+ partnum
;
1246 /* Nothing to do for type message */
1250 * Discrete types (text/application/audio/image/video)
1255 int i
, xstdout
, len
, buflen
;
1257 char *vec
[4], buffer
[BUFSIZ
];
1259 CI ci
= &ct
->c_ctinfo
;
1262 if (!(cp
= ci
->ci_magic
))
1263 adios (NULL
, "internal error(5)");
1265 if ((tfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1266 adios("mhbuildsbr", "unable to create temporary file in %s",
1269 ce
->ce_file
= add (tfile
, NULL
);
1274 /* Get buffer ready to go */
1277 buflen
= sizeof(buffer
);
1280 * Parse composition string into buffer
1282 for ( ; *cp
; cp
++) {
1287 /* insert parameters from directive */
1291 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1292 snprintf (bp
, buflen
, "%s%s=\"%s\"", s
,
1293 pm
->pm_name
, get_param_value(pm
, '?'));
1303 /* %f, and stdout is not-redirected */
1309 * insert temporary filename where
1310 * content should be written
1312 snprintf (bp
, buflen
, "%s", ce
->ce_file
);
1316 /* insert content subtype */
1317 strncpy (bp
, ci
->ci_subtype
, buflen
);
1321 /* insert character % */
1342 printf ("composing content %s/%s from command\n\t%s\n",
1343 ci
->ci_type
, ci
->ci_subtype
, buffer
);
1345 fflush (stdout
); /* not sure if need for -noverbose */
1352 if ((out
= fopen (ce
->ce_file
, "w")) == NULL
)
1353 adios (ce
->ce_file
, "unable to open for writing");
1355 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
> 5; i
++)
1359 adios ("fork", "unable to fork");
1364 dup2 (fileno (out
), 1);
1365 close (fileno (out
));
1366 execvp ("/bin/sh", vec
);
1367 fprintf (stderr
, "unable to exec ");
1374 if (pidXwait(child_id
, NULL
))
1380 /* Check size of file */
1381 if (listsw
&& ct
->c_end
== 0L) {
1384 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1385 ct
->c_end
= (long) st
.st_size
;
1397 * 1) choose a transfer encoding.
1398 * 2) check for clashes with multipart boundary string.
1399 * 3) for text content, figure out which character set is being used.
1401 * If there is a clash with one of the contents and the multipart boundary,
1402 * this function will exit with NOTOK. This will cause the scanning process
1403 * to be repeated with a different multipart boundary. It is possible
1404 * (although highly unlikely) that this scan will be repeated multiple times.
1408 scan_content (CT ct
, size_t maxunencoded
)
1411 int check8bit
= 0, contains8bit
= 0; /* check if contains 8bit data */
1412 int checknul
= 0, containsnul
= 0; /* check if contains NULs */
1413 int checklinelen
= 0, linelen
= 0; /* check for long lines */
1414 int checkllinelen
= 0; /* check for extra-long lines */
1415 int checkboundary
= 0, boundaryclash
= 0; /* check if clashes with multipart boundary */
1416 int checklinespace
= 0, linespace
= 0; /* check if any line ends with space */
1421 struct text
*t
= NULL
;
1423 CE ce
= &ct
->c_cefile
;
1426 * handle multipart by scanning all subparts
1427 * and then checking their encoding.
1429 if (ct
->c_type
== CT_MULTIPART
) {
1430 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1433 /* initially mark the domain of enclosing multipart as 7bit */
1434 ct
->c_encoding
= CE_7BIT
;
1436 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1437 CT p
= part
->mp_part
;
1439 if (scan_content (p
, maxunencoded
) == NOTOK
) /* choose encoding for subpart */
1442 /* if necessary, enlarge encoding for enclosing multipart */
1443 if (p
->c_encoding
== CE_BINARY
)
1444 ct
->c_encoding
= CE_BINARY
;
1445 if (p
->c_encoding
== CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
)
1446 ct
->c_encoding
= CE_8BIT
;
1453 * Decide what to check while scanning this content. Note that
1454 * for text content we always check for 8bit characters if the
1455 * charset is unspecified, because that controls whether or not the
1456 * character set is us-ascii or retrieved from the locale.
1459 if (ct
->c_type
== CT_TEXT
) {
1460 t
= (struct text
*) ct
->c_ctparams
;
1461 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
1467 switch (ct
->c_reqencoding
) {
1478 /* Use the default rules based on content-type */
1479 switch (ct
->c_type
) {
1483 if (ct
->c_subtype
== TEXT_PLAIN
) {
1490 case CT_APPLICATION
:
1502 /* don't check anything for message/external */
1503 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1516 * Don't check anything for these types,
1517 * since we are forcing use of base64, unless
1518 * the content-type was specified by a mhbuild directive.
1529 * Scan the unencoded content
1531 if (check8bit
|| checklinelen
|| checklinespace
|| checkboundary
||
1532 checkllinelen
|| checknul
) {
1533 if ((in
= fopen (ce
->ce_file
, "r")) == NULL
)
1534 adios (ce
->ce_file
, "unable to open for reading");
1535 prefix_len
= strlen (prefix
);
1537 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1539 * Check for 8bit and NUL data.
1541 for (cp
= bufp
; (check8bit
|| checknul
) &&
1542 cp
< bufp
+ gotlen
; cp
++) {
1543 if (!isascii ((unsigned char) *cp
)) {
1545 check8bit
= 0; /* no need to keep checking */
1549 checknul
= 0; /* no need to keep checking */
1554 * Check line length.
1556 if (checklinelen
&& ((size_t)gotlen
> maxunencoded
+ 1)) {
1558 checklinelen
= 0; /* no need to keep checking */
1562 * RFC 5322 specifies that a message cannot contain a line
1563 * greater than 998 characters (excluding the CRLF). If we
1564 * get one of those lines and linelen is NOT set, then abort.
1567 if (checkllinelen
&& !linelen
&&
1568 (gotlen
> MAXLONGLINE
+ 1)) {
1569 adios(NULL
, "Line in content exceeds maximum line limit (%d)",
1574 * Check if line ends with a space.
1576 if (checklinespace
&& (cp
= bufp
+ gotlen
- 2) > bufp
&&
1577 isspace ((unsigned char) *cp
)) {
1579 checklinespace
= 0; /* no need to keep checking */
1583 * Check if content contains a line that clashes
1584 * with our standard boundary for multipart messages.
1586 if (checkboundary
&& bufp
[0] == '-' && bufp
[1] == '-') {
1587 for (cp
= bufp
+ gotlen
- 1; cp
>= bufp
; cp
--)
1588 if (!isspace ((unsigned char) *cp
))
1591 if (!strncmp(bufp
+ 2, prefix
, prefix_len
) &&
1592 isdigit((unsigned char) bufp
[2 + prefix_len
])) {
1594 checkboundary
= 0; /* no need to keep checking */
1603 * If the content is text and didn't specify a character set,
1604 * we need to figure out which one was used.
1606 set_charset (ct
, contains8bit
);
1609 * Decide which transfer encoding to use.
1612 if (ct
->c_reqencoding
!= CE_UNKNOWN
)
1613 ct
->c_encoding
= ct
->c_reqencoding
;
1615 int wants_q_p
= (containsnul
|| linelen
|| linespace
|| checksw
);
1617 switch (ct
->c_type
) {
1620 ct
->c_encoding
= CE_QUOTED
;
1621 else if (contains8bit
)
1622 ct
->c_encoding
= CE_8BIT
;
1624 ct
->c_encoding
= CE_7BIT
;
1628 case CT_APPLICATION
:
1629 /* For application type, use base64, except when postscript */
1630 if (wants_q_p
|| contains8bit
) {
1631 if (ct
->c_subtype
== APPLICATION_POSTSCRIPT
)
1632 ct
->c_encoding
= CE_QUOTED
; /* historical */
1634 ct
->c_encoding
= CE_BASE64
;
1636 ct
->c_encoding
= CE_7BIT
;
1641 ct
->c_encoding
= contains8bit
? CE_8BIT
: CE_7BIT
;
1647 /* For audio, image, and video contents, just use base64 */
1648 ct
->c_encoding
= CE_BASE64
;
1653 return (boundaryclash
? NOTOK
: OK
);
1658 * Scan the content structures, and build header
1659 * fields that will need to be output into the
1664 build_headers (CT ct
, int header_encoding
)
1666 int cc
, mailbody
, extbody
, len
;
1667 char *np
, *vp
, buffer
[BUFSIZ
];
1668 CI ci
= &ct
->c_ctinfo
;
1671 * If message is type multipart, then add the multipart
1672 * boundary to the list of attribute/value pairs.
1674 if (ct
->c_type
== CT_MULTIPART
) {
1675 static int level
= 0; /* store nesting level */
1677 snprintf (buffer
, sizeof(buffer
), "%s%d", prefix
, level
++);
1678 add_param(&ci
->ci_first_pm
, &ci
->ci_last_pm
, "boundary", buffer
, 0);
1682 * Skip the output of Content-Type, parameters, content
1683 * description and disposition, and Content-ID if the
1684 * content is of type "message" and the rfc934 compatibility
1685 * flag is set (which means we are inside multipart/digest
1686 * and the switch -rfc934mode was given).
1688 if (ct
->c_type
== CT_MESSAGE
&& ct
->c_rfc934
)
1692 * output the content type and subtype
1694 np
= add (TYPE_FIELD
, NULL
);
1695 vp
= concat (" ", ci
->ci_type
, "/", ci
->ci_subtype
, NULL
);
1697 /* keep track of length of line */
1698 len
= strlen (TYPE_FIELD
) + strlen (ci
->ci_type
)
1699 + strlen (ci
->ci_subtype
) + 3;
1701 extbody
= ct
->c_type
== CT_MESSAGE
&& ct
->c_subtype
== MESSAGE_EXTERNAL
;
1702 mailbody
= extbody
&& ((struct exbody
*) ct
->c_ctparams
)->eb_body
;
1705 * Append the attribute/value pairs to
1706 * the end of the Content-Type line.
1709 if (ci
->ci_first_pm
) {
1710 char *s
= output_params(len
, ci
->ci_first_pm
, &len
, mailbody
);
1713 adios(NULL
, "Internal error: failed outputting Content-Type "
1721 * Append any RFC-822 comment to the end of
1722 * the Content-Type line.
1724 if (ci
->ci_comment
) {
1725 snprintf (buffer
, sizeof(buffer
), "(%s)", ci
->ci_comment
);
1726 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
1727 vp
= add ("\n\t", vp
);
1733 vp
= add (buffer
, vp
);
1736 vp
= add ("\n", vp
);
1737 add_header (ct
, np
, vp
);
1740 * output the Content-ID, unless disabled by -nocontentid
1742 if (contentidsw
&& ct
->c_id
) {
1743 np
= add (ID_FIELD
, NULL
);
1744 vp
= concat (" ", ct
->c_id
, NULL
);
1745 add_header (ct
, np
, vp
);
1748 * output the Content-Description
1751 np
= add (DESCR_FIELD
, NULL
);
1752 vp
= concat (" ", ct
->c_descr
, NULL
);
1753 if (header_encoding
!= CE_8BIT
) {
1754 if (encode_rfc2047(DESCR_FIELD
, &vp
, header_encoding
, NULL
)) {
1755 adios(NULL
, "Unable to encode %s header", DESCR_FIELD
);
1758 add_header (ct
, np
, vp
);
1762 * output the Content-Disposition. If it's NULL but c_dispo_type is
1763 * set, then we need to build it.
1766 np
= add (DISPO_FIELD
, NULL
);
1767 vp
= concat (" ", ct
->c_dispo
, NULL
);
1768 add_header (ct
, np
, vp
);
1769 } else if (ct
->c_dispo_type
) {
1770 vp
= concat (" ", ct
->c_dispo_type
, NULL
);
1771 len
= strlen(DISPO_FIELD
) + strlen(vp
) + 1;
1772 np
= output_params(len
, ct
->c_dispo_first
, NULL
, 0);
1777 add_header (ct
, getcpy(DISPO_FIELD
), vp
);
1782 * If this is the internal content structure for a
1783 * "message/external", then we are done with the
1784 * headers (since it has no body).
1790 * output the Content-MD5
1793 np
= add (MD5_FIELD
, NULL
);
1794 vp
= calculate_digest (ct
, (ct
->c_encoding
== CE_QUOTED
) ? 1 : 0);
1795 add_header (ct
, np
, vp
);
1799 * output the Content-Transfer-Encoding
1801 switch (ct
->c_encoding
) {
1803 /* Nothing to output */
1807 np
= add (ENCODING_FIELD
, NULL
);
1808 vp
= concat (" ", "8bit", "\n", NULL
);
1809 add_header (ct
, np
, vp
);
1813 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1814 adios (NULL
, "internal error, invalid encoding");
1816 np
= add (ENCODING_FIELD
, NULL
);
1817 vp
= concat (" ", "quoted-printable", "\n", NULL
);
1818 add_header (ct
, np
, vp
);
1822 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1823 adios (NULL
, "internal error, invalid encoding");
1825 np
= add (ENCODING_FIELD
, NULL
);
1826 vp
= concat (" ", "base64", "\n", NULL
);
1827 add_header (ct
, np
, vp
);
1831 if (ct
->c_type
== CT_MESSAGE
)
1832 adios (NULL
, "internal error, invalid encoding");
1834 np
= add (ENCODING_FIELD
, NULL
);
1835 vp
= concat (" ", "binary", "\n", NULL
);
1836 add_header (ct
, np
, vp
);
1840 adios (NULL
, "unknown transfer encoding in content");
1845 * Additional content specific header processing
1847 switch (ct
->c_type
) {
1850 struct multipart
*m
;
1853 m
= (struct multipart
*) ct
->c_ctparams
;
1854 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1858 build_headers (p
, header_encoding
);
1864 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1867 e
= (struct exbody
*) ct
->c_ctparams
;
1868 build_headers (e
->eb_content
, header_encoding
);
1881 static char nib2b64
[0x40+1] =
1882 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1885 calculate_digest (CT ct
, int asciiP
)
1890 unsigned char digest
[16];
1891 unsigned char outbuf
[25];
1893 CE ce
= &ct
->c_cefile
;
1894 char *infilename
= ce
->ce_file
? ce
->ce_file
: ct
->c_file
;
1898 if ((in
= fopen (infilename
, "r")) == NULL
)
1899 adios (infilename
, "unable to open for reading");
1901 /* Initialize md5 context */
1902 MD5Init (&mdContext
);
1904 /* calculate md5 message digest */
1909 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1912 cp
= bufp
+ gotlen
- 1;
1913 if ((c
= *cp
) == '\n')
1916 MD5Update (&mdContext
, (unsigned char *) bufp
,
1917 (unsigned int) gotlen
);
1920 MD5Update (&mdContext
, (unsigned char *) "\r\n", 2);
1923 char buffer
[BUFSIZ
];
1924 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), in
)) > 0)
1925 MD5Update (&mdContext
, (unsigned char *) buffer
, (unsigned int) cc
);
1928 /* md5 finalization. Write digest and zero md5 context */
1929 MD5Final (digest
, &mdContext
);
1934 /* print debugging info */
1938 fprintf (stderr
, "MD5 digest=");
1939 for (ep
= (dp
= digest
) + sizeof(digest
) / sizeof(digest
[0]);
1941 fprintf (stderr
, "%02x", *dp
& 0xff);
1942 fprintf (stderr
, "\n");
1945 /* encode the digest using base64 */
1946 for (dp
= digest
, op
= (char *) outbuf
,
1947 cc
= sizeof(digest
) / sizeof(digest
[0]);
1948 cc
> 0; cc
-= 3, op
+= 4) {
1952 bits
= (*dp
++ & 0xff) << 16;
1954 bits
|= (*dp
++ & 0xff) << 8;
1956 bits
|= *dp
++ & 0xff;
1959 for (bp
= op
+ 4; bp
> op
; bits
>>= 6)
1960 *--bp
= nib2b64
[bits
& 0x3f];
1968 /* null terminate string */
1971 /* now make copy and return string */
1972 vp
= concat (" ", outbuf
, "\n", NULL
);
1977 * Set things up for the content structure for file "filename" that
1982 setup_attach_content(CT ct
, char *filename
)
1984 char *type
, *simplename
= r1bindex(filename
, '/');
1985 struct str2init
*s2i
;
1988 if (! (type
= mime_type(filename
))) {
1989 adios(NULL
, "Unable to determine MIME type of \"%s\"", filename
);
1993 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1994 * since we're just feeding it a MIME type we have to add those ourself.
1995 * Map that to a valid content-type label and call any initialization
1999 if (get_ctinfo(type
, ct
, 0) == NOTOK
)
2004 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
2005 if (strcasecmp(ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0)
2007 if (!s2i
->si_key
&& !uprf(ct
->c_ctinfo
.ci_type
, "X-"))
2011 * Make sure the type isn't incompatible with what we can handle
2014 switch (ct
->c_type
= s2i
->si_val
) {
2016 adios (NULL
, "multipart types must be specified by mhbuild directives");
2020 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "partial") == 0)
2021 adios(NULL
, "Sorry, %s/%s isn't supported", ct
->c_ctinfo
.ci_type
,
2022 ct
->c_ctinfo
.ci_subtype
);
2023 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "external-body") == 0)
2024 adios(NULL
, "external-body messages must be specified "
2025 "by mhbuild directives");
2030 * This sets the subtype, if it's significant
2032 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
2033 (*ct
->c_ctinitfnx
)(ct
);
2038 * Feed in a few attributes; specifically, the name attribute, the
2039 * content-description, and the content-disposition.
2042 for (pm
= ct
->c_ctinfo
.ci_first_pm
; pm
; pm
= pm
->pm_next
) {
2043 if (strcasecmp(pm
->pm_name
, "name") == 0) {
2046 pm
->pm_value
= getcpy(simplename
);
2052 add_param(&ct
->c_ctinfo
.ci_first_pm
, &ct
->c_ctinfo
.ci_last_pm
,
2053 "name", simplename
, 0);
2055 ct
->c_descr
= getcpy(simplename
);
2056 ct
->c_descr
= add("\n", ct
->c_descr
);
2057 ct
->c_cefile
.ce_file
= getcpy(filename
);
2059 set_disposition (ct
);
2061 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename", simplename
, 0);
2065 * If disposition type hasn't already been set in ct:
2066 * Look for mhbuild-disposition-<type>/<subtype> entry
2067 * that specifies Content-Disposition type. Only
2068 * 'attachment' and 'inline' are allowed. Default to
2072 set_disposition (CT ct
) {
2073 if (ct
->c_dispo_type
== NULL
) {
2074 char *cp
= context_find_by_type ("disposition", ct
->c_ctinfo
.ci_type
,
2075 ct
->c_ctinfo
.ci_subtype
);
2077 if (cp
&& strcasecmp (cp
, "attachment") &&
2078 strcasecmp (cp
, "inline")) {
2079 admonish (NULL
, "configuration problem: %s-disposition-%s%s%s "
2080 "specifies '%s' but only 'attachment' and 'inline' are "
2081 "allowed", invo_name
,
2082 ct
->c_ctinfo
.ci_type
,
2083 ct
->c_ctinfo
.ci_subtype
? "/" : "",
2084 ct
->c_ctinfo
.ci_subtype
? ct
->c_ctinfo
.ci_subtype
: "",
2088 ct
->c_dispo_type
= cp
? getcpy (cp
) : getcpy ("attachment");
2093 * Set text content charset if it was unspecified. contains8bit
2095 * 0: content does not contain 8-bit characters
2096 * 1: content contains 8-bit characters
2097 * -1: ignore content and use user's locale to determine charset
2100 set_charset (CT ct
, int contains8bit
) {
2101 if (ct
->c_type
== CT_TEXT
) {
2104 if (ct
->c_ctparams
== NULL
) {
2105 if ((t
= ct
->c_ctparams
=
2106 (struct text
*) mh_xcalloc (1, sizeof (struct text
))) ==
2108 adios (NULL
, "out of memory");
2110 t
->tx_charset
= CHARSET_UNSPECIFIED
;
2112 t
= (struct text
*) ct
->c_ctparams
;
2115 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
2116 CI ci
= &ct
->c_ctinfo
;
2117 char *eightbitcharset
= write_charset_8bit();
2118 char *charset
= contains8bit
? eightbitcharset
: "us-ascii";
2120 if (contains8bit
== 1 &&
2121 strcasecmp (eightbitcharset
, "US-ASCII") == 0) {
2122 adios (NULL
, "Text content contains 8 bit characters, but "
2123 "character set is US-ASCII");
2126 add_param (&ci
->ci_first_pm
, &ci
->ci_last_pm
, "charset", charset
,
2129 t
->tx_charset
= CHARSET_SPECIFIED
;
2136 * Look at all of the replied-to message parts and expand any that
2137 * are matched by a pseudoheader. Except don't descend into
2141 expand_pseudoheaders (CT ct
, struct multipart
*m
, const char *infile
,
2142 const convert_list
*convert_head
) {
2143 /* text_plain_ct is used to concatenate all of the text/plain
2144 replies into one part, instead of having each one in a separate
2146 CT text_plain_ct
= NULL
;
2148 switch (ct
->c_type
) {
2149 case CT_MULTIPART
: {
2150 struct multipart
*mp
= (struct multipart
*) ct
->c_ctparams
;
2153 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
2156 /* The parts are in descending priority order (defined by
2157 RFC 2046 Sec. 5.1.4) because they were reversed by
2158 parse_mime (). So, stop looking for matches with
2159 immediate subparts after the first match of an
2161 for (part
= mp
->mp_parts
; ! matched
&& part
; part
= part
->mp_next
) {
2162 char *type_subtype
=
2163 concat (part
->mp_part
->c_ctinfo
.ci_type
, "/",
2164 part
->mp_part
->c_ctinfo
.ci_subtype
, NULL
);
2166 if (part
->mp_part
->c_type
== CT_MULTIPART
) {
2167 expand_pseudoheaders (part
->mp_part
, m
, infile
,
2170 const convert_list
*c
;
2172 for (c
= convert_head
; c
; c
= c
->next
) {
2173 if (! strcasecmp (type_subtype
, c
->type
)) {
2174 expand_pseudoheader (part
->mp_part
, &text_plain_ct
,
2176 c
->type
, c
->argstring
);
2182 free (type_subtype
);
2185 for (part
= mp
->mp_parts
; part
; part
= part
->mp_next
) {
2186 expand_pseudoheaders (part
->mp_part
, m
, infile
, convert_head
);
2193 char *type_subtype
=
2194 concat (ct
->c_ctinfo
.ci_type
, "/", ct
->c_ctinfo
.ci_subtype
,
2196 const convert_list
*c
;
2198 for (c
= convert_head
; c
; c
= c
->next
) {
2199 if (! strcasecmp (type_subtype
, c
->type
)) {
2200 expand_pseudoheader (ct
, &text_plain_ct
, m
, infile
, c
->type
,
2205 free (type_subtype
);
2213 * Expand a single pseudoheader. It's for the specified type.
2216 expand_pseudoheader (CT ct
, CT
*text_plain_ct
, struct multipart
*m
,
2217 const char *infile
, const char *type
,
2218 const char *argstring
) {
2220 FILE *reply_fp
= NULL
;
2221 char *convert
, *type_p
, *subtype_p
;
2222 char *convert_command
;
2223 char *charset
= NULL
;
2225 struct str2init
*s2i
;
2230 type_p
= getcpy (type
);
2231 if ((subtype_p
= strchr (type_p
, '/'))) {
2232 *subtype_p
++ = '\0';
2233 convert
= context_find_by_type ("convert", type_p
, subtype_p
);
2236 type_p
= concat ("mhbuild-convert-", type
, NULL
);
2237 convert
= context_find (type_p
);
2242 /* No mhbuild-convert- entry in mhn.defaults or profile
2246 /* reply_file is used to pass the output of the convert. */
2247 reply_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2249 concat (convert
, " ", argstring
? argstring
: "", " >", reply_file
,
2252 /* Convert here . . . */
2253 ct
->c_storeproc
= getcpy (convert_command
);
2254 ct
->c_umask
= ~m_gmprot ();
2256 if ((status
= show_content_aux (ct
, 0, convert_command
, NULL
, NULL
)) !=
2258 admonish (NULL
, "store of %s content failed", type
);
2260 free (convert_command
);
2262 /* Fill out the the new ct, reply_ct. */
2263 reply_ct
= (CT
) mh_xcalloc (1, sizeof *reply_ct
);
2264 init_decoded_content (reply_ct
, infile
);
2266 if (extract_headers (reply_ct
, reply_file
, &reply_fp
) == NOTOK
) {
2269 "failed to extract headers from convert output in %s",
2274 /* This sets reply_ct->c_ctparams, and reply_ct->c_termproc if the
2275 charset can't be handled natively. */
2276 for (s2i
= str2cts
; s2i
->si_key
; s2i
++) {
2277 if (strcasecmp(reply_ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0) {
2282 if ((reply_ct
->c_ctinitfnx
= s2i
->si_init
)) {
2283 (*reply_ct
->c_ctinitfnx
)(reply_ct
);
2287 get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1))) {
2288 /* The reply Content-Type had the charset. */
2291 set_charset (reply_ct
, -1);
2292 charset
= get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1);
2293 if (reply_ct
->c_reqencoding
== CE_UNKNOWN
) {
2294 /* Assume that 8bit is sufficient (for text). */
2295 reply_ct
->c_reqencoding
=
2296 strcasecmp (charset
, "US-ASCII") ? CE_8BIT
: CE_7BIT
;
2300 /* Concatenate text/plain parts. */
2301 if (reply_ct
->c_type
== CT_TEXT
&&
2302 reply_ct
->c_subtype
== TEXT_PLAIN
) {
2303 if (! *text_plain_ct
&& m
->mp_parts
&& m
->mp_parts
->mp_part
&&
2304 m
->mp_parts
->mp_part
->c_type
== CT_TEXT
&&
2305 m
->mp_parts
->mp_part
->c_subtype
== TEXT_PLAIN
) {
2306 *text_plain_ct
= m
->mp_parts
->mp_part
;
2307 /* Make sure that the charset is set in the text/plain
2309 set_charset (*text_plain_ct
, -1);
2310 if ((*text_plain_ct
)->c_reqencoding
== CE_UNKNOWN
) {
2311 /* Assume that 8bit is sufficient (for text). */
2312 (*text_plain_ct
)->c_reqencoding
=
2313 strcasecmp (charset
, "US-ASCII") ? CE_8BIT
: CE_7BIT
;
2317 if (*text_plain_ct
) {
2318 /* Only concatenate if the charsets are identical. */
2319 char *text_plain_ct_charset
=
2320 get_param ((*text_plain_ct
)->c_ctinfo
.ci_first_pm
, "charset",
2323 if (strcasecmp (text_plain_ct_charset
, charset
) == 0) {
2324 /* Append this text/plain reply to the first one.
2325 If there's a problem anywhere along the way,
2326 instead attach it is a separate part. */
2327 int text_plain_reply
=
2328 open ((*text_plain_ct
)->c_cefile
.ce_file
,
2329 O_WRONLY
| O_APPEND
);
2330 int addl_reply
= open (reply_file
, O_RDONLY
);
2332 if (text_plain_reply
!= NOTOK
&& addl_reply
!= NOTOK
) {
2333 /* Insert blank line before each addl part. */
2334 /* It would be nice not to do this for the first one. */
2335 if (write (text_plain_reply
, "\n", 1) == 1) {
2336 /* Copy the text from the new reply and
2337 then free its Content struct. */
2338 cpydata (addl_reply
, text_plain_reply
,
2339 (*text_plain_ct
)->c_cefile
.ce_file
,
2341 if (close (text_plain_reply
) == OK
&&
2342 close (addl_reply
) == OK
) {
2343 if (reply_fp
) { fclose (reply_fp
); }
2345 free_content (reply_ct
);
2352 *text_plain_ct
= reply_ct
;
2356 reply_ct
->c_cefile
.ce_file
= reply_file
;
2357 reply_ct
->c_cefile
.ce_fp
= reply_fp
;
2358 reply_ct
->c_cefile
.ce_unlink
= 1;
2360 /* Attach the new part to the parent mulitpart/mixed, "m". */
2361 part
= (struct part
*) mh_xcalloc (1, sizeof *part
);
2362 part
->mp_part
= reply_ct
;
2366 for (p
= m
->mp_parts
; p
&& p
->mp_next
; p
= p
->mp_next
) { continue; }
2374 /* Extract any Content-Type header from beginning of convert output. */
2376 extract_headers (CT ct
, char *reply_file
, FILE **reply_fp
) {
2377 char *buffer
= NULL
, *cp
, *end_of_header
;
2378 int found_header
= 0;
2379 struct stat statbuf
;
2381 /* Read the convert reply from the file to memory. */
2382 if (stat (reply_file
, &statbuf
) == NOTOK
) {
2383 admonish (reply_file
, "failed to stat");
2384 goto failed_to_extract_ct
;
2387 buffer
= mh_xmalloc (statbuf
.st_size
+ 1);
2389 if ((*reply_fp
= fopen (reply_file
, "r+")) == NULL
||
2390 fread (buffer
, 1, (size_t) statbuf
.st_size
, *reply_fp
) <
2391 (size_t) statbuf
.st_size
) {
2392 admonish (reply_file
, "failed to read");
2393 goto failed_to_extract_ct
;
2395 buffer
[statbuf
.st_size
] = '\0';
2397 /* Look for a header in the convert reply. */
2398 if (strncasecmp (buffer
, TYPE_FIELD
, strlen (TYPE_FIELD
)) == 0 &&
2399 buffer
[strlen (TYPE_FIELD
)] == ':') {
2400 if ((end_of_header
= strstr (buffer
, "\r\n\r\n"))) {
2403 } else if ((end_of_header
= strstr (buffer
, "\n\n"))) {
2415 /* Truncate buffer to just the C-T. */
2416 *end_of_header
= '\0';
2417 n
= strlen (buffer
);
2419 if (get_ctinfo (buffer
+ 14, ct
, 0) != OK
) {
2420 admonish (NULL
, "unable to get content info for reply");
2421 goto failed_to_extract_ct
;
2424 /* Hack. Use parse_mime() to detect the type/subtype of the
2425 reply, which we'll use below. */
2426 tmp_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2427 if ((tmp_f
= fopen (tmp_file
, "w")) &&
2428 fwrite (buffer
, 1, n
, tmp_f
) == n
) {
2431 goto failed_to_extract_ct
;
2433 tmp_ct
= parse_mime (tmp_file
);
2436 /* The type and subtype were detected from the reply
2437 using parse_mime() above. */
2438 ct
->c_type
= tmp_ct
->c_type
;
2439 ct
->c_subtype
= tmp_ct
->c_subtype
;
2440 free_content (tmp_ct
);
2445 /* Rewrite the content without the header. */
2446 cp
= end_of_header
+ 1;
2449 if (fwrite (cp
, 1, statbuf
.st_size
- (cp
- buffer
), *reply_fp
) <
2450 (size_t) (statbuf
.st_size
- (cp
- buffer
))) {
2451 admonish (reply_file
, "failed to write");
2452 goto failed_to_extract_ct
;
2455 if (ftruncate (fileno (*reply_fp
), statbuf
.st_size
- (cp
- buffer
)) !=
2457 advise (reply_file
, "ftruncate");
2458 goto failed_to_extract_ct
;
2461 /* No header section, assume the reply is text/plain. */
2462 ct
->c_type
= CT_TEXT
;
2463 ct
->c_subtype
= TEXT_PLAIN
;
2464 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
) {
2465 /* This never should fail, but just in case. */
2466 adios (NULL
, "unable to get content info for reply");
2470 /* free_encoding() will close reply_fp, which is passed through
2471 ct->c_cefile.ce_fp. */
2475 failed_to_extract_ct
:
2476 if (*reply_fp
) { fclose (*reply_fp
); }