1 /* mhbuildsbr.c -- routines to expand/translate MIME composition files
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
9 * This code was originally part of mhn.c. I split it into
10 * a separate program (mhbuild.c) and then later split it
11 * again (mhbuildsbr.c). But the code still has some of
12 * the mhn.c code in it. This program needs additional
13 * streamlining and removal of unneeded code.
22 #include <h/mhparse.h>
25 #ifdef HAVE_SYS_TIME_H
26 # include <sys/time.h>
35 extern int contentidsw
;
38 extern int rcachesw
; /* mhcachesbr.c */
39 extern int wcachesw
; /* mhcachesbr.c */
41 static char prefix
[] = "----- =_aaaaaaaaaa";
45 struct attach_list
*next
;
48 typedef struct convert_list
{
52 struct convert_list
*next
;
57 void content_error (char *, CT
, char *, ...);
60 int find_cache (CT
, int, int *, char *, char *, int);
64 void freects_done (int) NORETURN
;
65 void free_ctinfo (CT
);
66 void free_encoding (CT
, int);
71 static int init_decoded_content (CT
, const char *);
72 static void setup_attach_content(CT
, char *);
73 static void set_disposition (CT
);
74 static void set_charset (CT
, int);
75 static void expand_pseudoheaders (CT
, struct multipart
*, const char *,
76 const convert_list
*);
77 static void expand_pseudoheader (CT
, CT
*, struct multipart
*, const char *,
78 const char *, const char *);
79 static char *fgetstr (char *, int, FILE *);
80 static int user_content (FILE *, char *, CT
*, const char *infilename
);
81 static void set_id (CT
, int);
82 static int compose_content (CT
, int);
83 static int scan_content (CT
, size_t);
84 static int build_headers (CT
, int);
85 static char *calculate_digest (CT
, int);
86 static int extract_headers (CT
, char *, FILE **);
89 static unsigned char directives_stack
[32];
90 static unsigned int directives_index
;
92 static int do_direct(void)
94 return directives_stack
[directives_index
];
97 static void directive_onoff(int onoff
)
99 if (directives_index
>= sizeof(directives_stack
) - 1) {
100 fprintf(stderr
, "mhbuild: #on/off overflow, continuing\n");
103 directives_stack
[++directives_index
] = onoff
;
106 static void directive_init(int onoff
)
108 directives_index
= 0;
109 directives_stack
[0] = onoff
;
112 static void directive_pop(void)
114 if (directives_index
> 0)
117 fprintf(stderr
, "mhbuild: #pop underflow, continuing\n");
121 * Main routine for translating composition file
122 * into valid MIME message. It translates the draft
123 * into a content structure (actually a tree of content
124 * structures). This message then can be manipulated
125 * in various ways, including being output via
130 build_mime (char *infile
, int autobuild
, int dist
, int directives
,
131 int header_encoding
, size_t maxunencoded
, int verbose
)
134 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
141 m_getfld_state_t gstate
= 0;
142 struct attach_list
*attach_head
= NULL
, *attach_tail
= NULL
, *at_entry
;
143 convert_list
*convert_head
= NULL
, *convert_tail
= NULL
, *convert
;
145 directive_init(directives
);
147 umask (~m_gmprot ());
149 /* open the composition draft */
150 if ((in
= fopen (infile
, "r")) == NULL
)
151 adios (infile
, "unable to open for reading");
154 * Allocate space for primary (outside) content
159 * Allocate structure for handling decoded content
160 * for this part. We don't really need this, but
161 * allocate it to remain consistent.
163 init_decoded_content (ct
, infile
);
166 * Parse some of the header fields in the composition
167 * draft into the linked list of header fields for
168 * the new MIME message.
170 m_getfld_track_filepos (&gstate
, in
);
171 for (compnum
= 1;;) {
172 int bufsz
= sizeof buf
;
173 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
178 /* abort if draft has Mime-Version or C-T-E header field */
179 if (strcasecmp (name
, VRSN_FIELD
) == 0 ||
180 strcasecmp (name
, ENCODING_FIELD
) == 0) {
186 adios (NULL
, "draft shouldn't contain %s: field", name
);
189 /* ignore any Content-Type fields in the header */
190 if (!strcasecmp (name
, TYPE_FIELD
)) {
191 while (state
== FLDPLUS
) {
193 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
198 /* get copies of the buffers */
199 np
= mh_xstrdup(name
);
200 vp
= mh_xstrdup(buf
);
202 /* if necessary, get rest of field */
203 while (state
== FLDPLUS
) {
205 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
206 vp
= add (buf
, vp
); /* add to previous value */
210 * Now add the header data to the list, unless it's an attach
211 * header; in that case, add it to our attach list
214 if (strcasecmp(ATTACH_FIELD
, np
) == 0 ||
215 strcasecmp(ATTACH_FIELD_ALT
, np
) == 0) {
216 struct attach_list
*entry
;
217 char *s
= vp
, *e
= vp
+ strlen(vp
) - 1;
221 * Make sure we can find the start of this filename.
222 * If it's blank, we skip completely. Otherwise, strip
223 * off any leading spaces and trailing newlines.
226 while (isspace((unsigned char) *s
))
229 while (e
> s
&& *e
== '\n')
238 entry
->filename
= mh_xstrdup(s
);
243 attach_tail
->next
= entry
;
246 attach_head
= attach_tail
= entry
;
248 } else if (strncasecmp(MHBUILD_FILE_PSEUDOHEADER
, np
,
249 strlen (MHBUILD_FILE_PSEUDOHEADER
)) == 0) {
251 * Nmh-mhbuild-file-text/calendar: /home/user/Mail/inbox/9
253 char *type
= np
+ strlen (MHBUILD_FILE_PSEUDOHEADER
);
256 /* vp should begin with a space because m_getfld()
257 includes the space after the colon in buf. */
258 while (isspace((unsigned char) *filename
)) { ++filename
; }
259 /* Trim trailing newline and any other whitespace. */
262 for (convert
= convert_head
; convert
; convert
= convert
->next
) {
263 if (strcasecmp (convert
->type
, type
) == 0) { break; }
266 if (convert
->filename
&&
267 strcasecmp (convert
->filename
, filename
)) {
268 adios (NULL
, "Multiple %s headers with different files"
269 " not allowed", type
);
271 convert
->filename
= mh_xstrdup(filename
);
275 convert
->filename
= mh_xstrdup(filename
);
276 convert
->type
= mh_xstrdup(type
);
279 convert_tail
->next
= convert
;
281 convert_head
= convert
;
283 convert_tail
= convert
;
288 } else if (strncasecmp(MHBUILD_ARGS_PSEUDOHEADER
, np
,
289 strlen (MHBUILD_ARGS_PSEUDOHEADER
)) == 0) {
291 * Nmh-mhbuild-args-text/calendar: -reply accept
293 char *type
= np
+ strlen (MHBUILD_ARGS_PSEUDOHEADER
);
294 char *argstring
= vp
;
296 /* vp should begin with a space because m_getfld()
297 includes the space after the colon in buf. */
298 while (isspace((unsigned char) *argstring
)) { ++argstring
; }
299 /* Trim trailing newline and any other whitespace. */
302 for (convert
= convert_head
; convert
; convert
= convert
->next
) {
303 if (strcasecmp (convert
->type
, type
) == 0) { break; }
306 if (convert
->argstring
&&
307 strcasecmp (convert
->argstring
, argstring
)) {
308 adios (NULL
, "Multiple %s headers with different "
309 "argstrings not allowed", type
);
311 convert
->argstring
= mh_xstrdup(argstring
);
315 convert
->type
= mh_xstrdup(type
);
316 convert
->argstring
= mh_xstrdup(argstring
);
319 convert_tail
->next
= convert
;
321 convert_head
= convert
;
323 convert_tail
= convert
;
329 add_header (ct
, np
, vp
);
333 /* if this wasn't the last header field, then continue */
337 fseek (in
, (long) (-strlen (buf
)), SEEK_CUR
);
344 adios (NULL
, "message format error in component #%d", compnum
);
347 adios (NULL
, "getfld() returned %d", state
);
351 m_getfld_state_destroy (&gstate
);
353 if (header_encoding
!= CE_8BIT
) {
355 * Iterate through the list of headers and call the function to MIME-ify
359 for (hp
= ct
->c_first_hf
; hp
!= NULL
; hp
= hp
->next
) {
360 if (encode_rfc2047(hp
->name
, &hp
->value
, header_encoding
, NULL
)) {
361 adios(NULL
, "Unable to encode header \"%s\"", hp
->name
);
367 * Now add the MIME-Version header field
368 * to the list of header fields.
372 np
= mh_xstrdup(VRSN_FIELD
);
373 vp
= concat (" ", VRSN_VALUE
, "\n", NULL
);
374 add_header (ct
, np
, vp
);
378 * We initially assume we will find multiple contents in the
379 * draft. So create a multipart/mixed content to hold everything.
380 * We can remove this later, if it is not needed.
382 if (get_ctinfo ("multipart/mixed", ct
, 0) == NOTOK
)
384 ct
->c_type
= CT_MULTIPART
;
385 ct
->c_subtype
= MULTI_MIXED
;
388 ct
->c_ctparams
= (void *) m
;
392 * read and parse the composition file
393 * and the directives it contains.
395 while (fgetstr (buf
, sizeof(buf
) - 1, in
)) {
399 if (user_content (in
, buf
, &p
, infile
) == DONE
) {
400 inform("ignoring spurious #end, continuing...");
413 * Add any Attach headers to the list of MIME parts at the end of the
417 for (at_entry
= attach_head
; at_entry
; ) {
418 struct attach_list
*at_prev
= at_entry
;
422 if (access(at_entry
->filename
, R_OK
) != 0) {
423 adios("reading", "Unable to open %s for", at_entry
->filename
);
427 init_decoded_content(p
, infile
);
430 * Initialize our content structure based on the filename,
431 * and fill in all of the relevant fields. Also place MIME
432 * parameters in the attributes array.
435 setup_attach_content(p
, at_entry
->filename
);
442 at_entry
= at_entry
->next
;
443 free(at_prev
->filename
);
448 * Handle the mhbuild pseudoheaders, which deal with specific
457 /* In case there are multiple calls that land here, prevent leak. */
458 for (ctp
= cts
; ctp
&& *ctp
; ++ctp
) { free_content (*ctp
); }
461 /* Extract the type part (as a CT) from filename. */
462 cts
= mh_xcalloc(2, sizeof *cts
);
463 if (! (cts
[0] = parse_mime (convert_head
->filename
))) {
464 adios (NULL
, "failed to parse %s", convert_head
->filename
);
467 expand_pseudoheaders (cts
[0], m
, infile
, convert_head
);
469 /* Free the convert list. */
470 for (convert
= convert_head
; convert
; convert
= next
) {
471 next
= convert
->next
;
472 free (convert
->type
);
473 free (convert
->filename
);
474 free (convert
->argstring
);
481 * To allow for empty message bodies, if we've found NO content at all
482 * yet cook up an empty text/plain part.
491 init_decoded_content(p
, infile
);
493 if (get_ctinfo ("text/plain", p
, 0) == NOTOK
)
497 p
->c_subtype
= TEXT_PLAIN
;
498 p
->c_encoding
= CE_7BIT
;
500 * Sigh. ce_file contains the "decoded" contents of this part.
501 * So this seems like the best option available since we're going
502 * to call scan_content() on this.
504 p
->c_cefile
.ce_file
= mh_xstrdup("/dev/null");
505 p
->c_begin
= ftell(in
);
506 p
->c_end
= ftell(in
);
509 t
->tx_charset
= CHARSET_SPECIFIED
;
518 * close the composition draft since
519 * it's not needed any longer.
524 * If only one content was found, then remove and
525 * free the outer multipart content.
527 if (!m
->mp_parts
->mp_next
) {
530 p
= m
->mp_parts
->mp_part
;
531 m
->mp_parts
->mp_part
= NULL
;
533 /* move header fields */
534 p
->c_first_hf
= ct
->c_first_hf
;
535 p
->c_last_hf
= ct
->c_last_hf
;
536 ct
->c_first_hf
= NULL
;
537 ct
->c_last_hf
= NULL
;
546 * Fill out, or expand directives. Parse and execute
547 * commands specified by profile composition strings.
549 compose_content (ct
, verbose
);
551 if ((cp
= strchr(prefix
, 'a')) == NULL
)
552 adios (NULL
, "internal error(4)");
555 * If using EAI, force 8-bit charset.
557 if (header_encoding
== CE_8BIT
) {
562 * Scan the contents. Choose a transfer encoding, and
563 * check if prefix for multipart boundary clashes with
564 * any of the contents.
566 while (scan_content (ct
, maxunencoded
) == NOTOK
) {
571 adios (NULL
, "giving up trying to find a unique delimiter string");
577 /* Build the rest of the header field structures */
579 build_headers (ct
, header_encoding
);
586 * Set up structures for placing unencoded
587 * content when building parts.
591 init_decoded_content (CT ct
, const char *filename
)
593 ct
->c_ceopenfnx
= open7Bit
; /* since unencoded */
594 ct
->c_ceclosefnx
= close_encoding
;
595 ct
->c_cesizefnx
= NULL
; /* since unencoded */
596 ct
->c_encoding
= CE_7BIT
; /* Seems like a reasonable default */
597 ct
->c_file
= add(filename
, NULL
);
604 fgetstr (char *s
, int n
, FILE *stream
)
610 for (cp
= s
; cp
< ep
;) {
613 if (!fgets (cp
, n
, stream
))
614 return cp
== s
? NULL
: s
; /* "\\\nEOF" ignored. */
616 if (! do_direct() || (cp
== s
&& *cp
!= '#'))
617 return s
; /* Plaintext line. */
621 break; /* Can't contain "\\\n". */
622 cp
+= len
- 1; /* Just before NUL. */
623 if (*cp
-- != '\n' || *cp
!= '\\')
625 *cp
= '\0'; /* Erase the trailing "\\\n". */
629 if (strcmp(s
, "#on\n") == 0) {
631 } else if (strcmp(s
, "#off\n") == 0) {
633 } else if (strcmp(s
, "#pop\n") == 0) {
643 * Parse the composition draft for text and directives.
644 * Do initial setup of Content structure.
648 user_content (FILE *in
, char *buf
, CT
*ctp
, const char *infilename
)
656 struct str2init
*s2i
;
661 if (buf
[0] == '\n' || (do_direct() && strcmp (buf
, "#\n") == 0)) {
666 /* allocate basic Content structure */
670 /* allocate basic structure for handling decoded content */
671 init_decoded_content (ct
, infilename
);
678 * Handle inline text. Check if line
679 * is one of the following forms:
681 * 1) doesn't begin with '#' (implicit directive)
682 * 2) begins with "##" (implicit directive)
683 * 3) begins with "#<"
685 if (!do_direct() || buf
[0] != '#' || buf
[1] == '#' || buf
[1] == '<') {
689 char content
[BUFSIZ
];
693 if ((cp
= m_mktemp2(NULL
, invo_name
, NULL
, &out
)) == NULL
) {
694 adios("mhbuildsbr", "unable to create temporary file in %s",
698 /* use a temp file to collect the plain text lines */
699 ce
->ce_file
= mh_xstrdup(cp
);
702 if (do_direct() && (buf
[0] == '#' && buf
[1] == '<')) {
703 strncpy (content
, buf
+ 2, sizeof(content
));
709 /* the directive is implicit */
710 strncpy (content
, "text/plain", sizeof(content
));
712 strncpy (buffer
, (!do_direct() || buf
[0] != '#') ? buf
: buf
+ 1, sizeof(buffer
));
716 if (headers
>= 0 && do_direct() && uprf (buffer
, DESCR_FIELD
)
717 && buffer
[i
= strlen (DESCR_FIELD
)] == ':') {
721 ct
->c_descr
= add (buffer
+ i
+ 1, ct
->c_descr
);
722 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
723 adios (NULL
, "end-of-file after %s: field in plaintext", DESCR_FIELD
);
731 adios (NULL
, "#-directive after %s: field in plaintext", DESCR_FIELD
);
739 if (headers
>= 0 && do_direct() && uprf (buffer
, DISPO_FIELD
)
740 && buffer
[i
= strlen (DISPO_FIELD
)] == ':') {
744 ct
->c_dispo
= add (buffer
+ i
+ 1, ct
->c_dispo
);
745 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
746 adios (NULL
, "end-of-file after %s: field in plaintext", DISPO_FIELD
);
754 adios (NULL
, "#-directive after %s: field in plaintext", DISPO_FIELD
);
762 if (headers
!= 1 || buffer
[0] != '\n')
768 if ((cp
= fgetstr (buffer
, sizeof(buffer
) - 1, in
)) == NULL
)
770 if (do_direct() && buffer
[0] == '#') {
773 if (buffer
[1] != '#')
775 for (cp
= (bp
= buffer
) + 1; *cp
; cp
++)
782 ct
->c_end
= ftell (out
);
785 /* parse content type */
786 if (get_ctinfo (content
, ct
, inlineD
) == NOTOK
)
789 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
790 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
792 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
796 * check type specified (possibly implicitly)
798 switch (ct
->c_type
= s2i
->si_val
) {
800 if (!strcasecmp (ci
->ci_subtype
, "rfc822")) {
801 ct
->c_encoding
= CE_7BIT
;
806 adios (NULL
, "it doesn't make sense to define an in-line %s content",
807 ct
->c_type
== CT_MESSAGE
? "message" : "multipart");
812 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
813 (*ct
->c_ctinitfnx
) (ct
);
818 fseek (in
, pos
, SEEK_SET
);
823 * If we've reached this point, the next line
824 * must be some type of explicit directive.
827 /* check if directive is external-type */
828 extrnal
= (buf
[1] == '@');
830 /* parse directive */
831 if (get_ctinfo (buf
+ (extrnal
? 2 : 1), ct
, 1) == NOTOK
)
834 /* check directive against the list of MIME types */
835 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
836 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
840 * Check if the directive specified a valid type.
841 * This will happen if it was one of the following forms:
848 adios (NULL
, "missing subtype in \"#%s\"", ci
->ci_type
);
850 switch (ct
->c_type
= s2i
->si_val
) {
852 adios (NULL
, "use \"#begin ... #end\" instead of \"#%s/%s\"",
853 ci
->ci_type
, ci
->ci_subtype
);
857 if (!strcasecmp (ci
->ci_subtype
, "partial"))
858 adios (NULL
, "sorry, \"#%s/%s\" isn't supported",
859 ci
->ci_type
, ci
->ci_subtype
);
860 if (!strcasecmp (ci
->ci_subtype
, "external-body"))
861 adios (NULL
, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
862 ci
->ci_type
, ci
->ci_subtype
);
865 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
866 ci
->ci_type
, ci
->ci_subtype
);
870 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
871 (*ct
->c_ctinitfnx
) (ct
);
876 * #@type/subtype (external types directive)
883 adios (NULL
, "need external information for \"#@%s/%s\"",
884 ci
->ci_type
, ci
->ci_subtype
);
887 snprintf (buffer
, sizeof(buffer
), "message/external-body; %s", ci
->ci_magic
);
892 * Since we are using the current Content structure to
893 * hold information about the type of the external
894 * reference, we need to create another Content structure
895 * for the message/external-body to wrap it in.
898 init_decoded_content(ct
, infilename
);
900 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
902 ct
->c_type
= CT_MESSAGE
;
903 ct
->c_subtype
= MESSAGE_EXTERNAL
;
906 ct
->c_ctparams
= (void *) e
;
912 if (params_external (ct
, 1) == NOTOK
)
918 /* Handle [file] argument */
920 /* check if specifies command to execute */
921 if (*ci
->ci_magic
== '|' || *ci
->ci_magic
== '!') {
922 for (cp
= ci
->ci_magic
+ 1; isspace ((unsigned char) *cp
); cp
++)
925 adios (NULL
, "empty pipe command for #%s directive", ci
->ci_type
);
930 /* record filename of decoded contents */
931 ce
->ce_file
= ci
->ci_magic
;
932 if (access (ce
->ce_file
, R_OK
) == NOTOK
)
933 adios ("reading", "unable to access %s for", ce
->ce_file
);
934 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
935 ct
->c_end
= (long) st
.st_size
;
942 * No [file] argument, so check profile for
943 * method to compose content.
945 cp
= context_find_by_type ("compose", ci
->ci_type
, ci
->ci_subtype
);
947 content_error (NULL
, ct
, "don't know how to compose content");
950 ci
->ci_magic
= mh_xstrdup(cp
);
955 adios (NULL
, "external definition not allowed for \"#%s\"", ci
->ci_type
);
959 * #forw [+folder] [msgs]
961 if (!strcasecmp (ci
->ci_type
, "forw")) {
963 char *folder
, *arguments
[MAXARGS
];
967 ap
= brkstring (ci
->ci_magic
, " ", "\n");
968 copyip (ap
, arguments
, MAXARGS
);
970 arguments
[0] = "cur";
975 /* search the arguments for a folder name */
976 for (ap
= arguments
; *ap
; ap
++) {
978 if (*cp
== '+' || *cp
== '@') {
980 adios (NULL
, "only one folder per #forw directive");
982 folder
= pluspath (cp
);
986 /* else, use the current folder */
988 folder
= mh_xstrdup(getfolder(1));
990 if (!(mp
= folder_read (folder
, 0)))
991 adios (NULL
, "unable to read folder %s", folder
);
992 for (ap
= arguments
; *ap
; ap
++) {
994 if (*cp
!= '+' && *cp
!= '@')
995 if (!m_convert (mp
, cp
))
1002 * If there is more than one message to include, make this
1003 * a content of type "multipart/digest" and insert each message
1004 * as a subpart. If there is only one message, then make this
1005 * a content of type "message/rfc822".
1007 if (mp
->numsel
> 1) {
1008 /* we are forwarding multiple messages */
1009 if (get_ctinfo ("multipart/digest", ct
, 0) == NOTOK
)
1011 ct
->c_type
= CT_MULTIPART
;
1012 ct
->c_subtype
= MULTI_DIGEST
;
1015 ct
->c_ctparams
= (void *) m
;
1018 for (msgnum
= mp
->lowsel
; msgnum
<= mp
->hghsel
; msgnum
++) {
1019 if (is_selected(mp
, msgnum
)) {
1025 init_decoded_content (p
, infilename
);
1027 if (get_ctinfo ("message/rfc822", p
, 0) == NOTOK
)
1029 p
->c_type
= CT_MESSAGE
;
1030 p
->c_subtype
= MESSAGE_RFC822
;
1032 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1033 pe
->ce_file
= mh_xstrdup(buffer
);
1034 if (listsw
&& stat (pe
->ce_file
, &st
) != NOTOK
)
1035 p
->c_end
= (long) st
.st_size
;
1039 pp
= &part
->mp_next
;
1044 /* we are forwarding one message */
1045 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
1047 ct
->c_type
= CT_MESSAGE
;
1048 ct
->c_subtype
= MESSAGE_RFC822
;
1050 msgnum
= mp
->lowsel
;
1051 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
1052 ce
->ce_file
= mh_xstrdup(buffer
);
1053 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
1054 ct
->c_end
= (long) st
.st_size
;
1057 folder_free (mp
); /* free folder/message structure */
1064 if (!strcasecmp (ci
->ci_type
, "end")) {
1071 * #begin [ alternative | parallel ]
1073 if (!strcasecmp (ci
->ci_type
, "begin")) {
1074 if (!ci
->ci_magic
) {
1076 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1077 } else if (!strcasecmp (ci
->ci_magic
, "alternative")) {
1078 vrsn
= MULTI_ALTERNATE
;
1079 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1080 } else if (!strcasecmp (ci
->ci_magic
, "parallel")) {
1081 vrsn
= MULTI_PARALLEL
;
1082 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
1083 } else if (uprf (ci
->ci_magic
, "digest")) {
1086 vrsn
= MULTI_UNKNOWN
;
1091 snprintf (buffer
, sizeof(buffer
), "multipart/%s", cp
);
1092 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
1094 ct
->c_type
= CT_MULTIPART
;
1095 ct
->c_subtype
= vrsn
;
1098 ct
->c_ctparams
= (void *) m
;
1101 while (fgetstr (buffer
, sizeof(buffer
) - 1, in
)) {
1105 if (user_content (in
, buffer
, &p
, infilename
) == DONE
) {
1107 adios (NULL
, "empty \"#begin ... #end\" sequence");
1115 pp
= &part
->mp_next
;
1118 inform("premature end-of-file, missing #end, continuing...");
1125 adios (NULL
, "unknown directive \"#%s\"", ci
->ci_type
);
1126 return NOTOK
; /* NOT REACHED */
1131 set_id (CT ct
, int top
)
1133 char contentid
[BUFSIZ
];
1135 static time_t clock
= 0;
1136 static char *msgfmt
;
1140 snprintf (contentid
, sizeof(contentid
), "%s\n", message_id (clock
, 1));
1142 msgfmt
= mh_xstrdup(contentid
);
1144 snprintf (contentid
, sizeof(contentid
), msgfmt
, top
? 0 : ++partno
);
1145 ct
->c_id
= mh_xstrdup(contentid
);
1150 * Fill out, or expand the various contents in the composition
1151 * draft. Read-in any necessary files. Parse and execute any
1152 * commands specified by profile composition strings.
1156 compose_content (CT ct
, int verbose
)
1158 CE ce
= &ct
->c_cefile
;
1160 switch (ct
->c_type
) {
1165 char partnam
[BUFSIZ
];
1166 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1170 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1171 pp
= partnam
+ strlen (partnam
);
1176 /* first, we call compose_content on all the subparts */
1177 for (part
= m
->mp_parts
, partnum
= 1; part
; part
= part
->mp_next
, partnum
++) {
1178 CT p
= part
->mp_part
;
1180 sprintf (pp
, "%d", partnum
);
1181 p
->c_partno
= mh_xstrdup(partnam
);
1182 if (compose_content (p
, verbose
) == NOTOK
)
1187 * If the -rfc934mode switch is given, then check all
1188 * the subparts of a multipart/digest. If they are all
1189 * message/rfc822, then mark this content and all
1190 * subparts with the rfc934 compatibility mode flag.
1192 if (rfc934sw
&& ct
->c_subtype
== MULTI_DIGEST
) {
1195 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1196 CT p
= part
->mp_part
;
1198 if (p
->c_subtype
!= MESSAGE_RFC822
) {
1203 ct
->c_rfc934
= is934
;
1204 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1205 CT p
= part
->mp_part
;
1207 if ((p
->c_rfc934
= is934
))
1213 ct
->c_end
= (partnum
= strlen (prefix
) + 2) + 2;
1217 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
)
1218 ct
->c_end
+= part
->mp_part
->c_end
+ partnum
;
1224 /* Nothing to do for type message */
1228 * Discrete types (text/application/audio/image/video)
1233 int i
, xstdout
, len
, buflen
;
1235 char *vec
[4], buffer
[BUFSIZ
];
1237 CI ci
= &ct
->c_ctinfo
;
1240 if (!(cp
= ci
->ci_magic
))
1241 adios (NULL
, "internal error(5)");
1243 if ((tfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1244 adios("mhbuildsbr", "unable to create temporary file in %s",
1247 ce
->ce_file
= mh_xstrdup(tfile
);
1252 /* Get buffer ready to go */
1255 buflen
= sizeof(buffer
);
1258 * Parse composition string into buffer
1260 for ( ; *cp
; cp
++) {
1265 /* insert parameters from directive */
1269 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1270 snprintf (bp
, buflen
, "%s%s=\"%s\"", s
,
1271 pm
->pm_name
, get_param_value(pm
, '?'));
1281 /* %f, and stdout is not-redirected */
1287 * insert temporary filename where
1288 * content should be written
1290 snprintf (bp
, buflen
, "%s", ce
->ce_file
);
1294 /* insert content subtype */
1295 strncpy (bp
, ci
->ci_subtype
, buflen
);
1299 /* insert character % */
1320 printf ("composing content %s/%s from command\n\t%s\n",
1321 ci
->ci_type
, ci
->ci_subtype
, buffer
);
1323 fflush (stdout
); /* not sure if need for -noverbose */
1330 if ((out
= fopen (ce
->ce_file
, "w")) == NULL
)
1331 adios (ce
->ce_file
, "unable to open for writing");
1333 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
> 5; i
++)
1337 adios ("fork", "unable to fork");
1342 dup2 (fileno (out
), 1);
1343 close (fileno (out
));
1344 execvp ("/bin/sh", vec
);
1345 fprintf (stderr
, "unable to exec ");
1352 if (pidXwait(child_id
, NULL
))
1358 /* Check size of file */
1359 if (listsw
&& ct
->c_end
== 0L) {
1362 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1363 ct
->c_end
= (long) st
.st_size
;
1375 * 1) choose a transfer encoding.
1376 * 2) check for clashes with multipart boundary string.
1377 * 3) for text content, figure out which character set is being used.
1379 * If there is a clash with one of the contents and the multipart boundary,
1380 * this function will exit with NOTOK. This will cause the scanning process
1381 * to be repeated with a different multipart boundary. It is possible
1382 * (although highly unlikely) that this scan will be repeated multiple times.
1386 scan_content (CT ct
, size_t maxunencoded
)
1389 int check8bit
= 0, contains8bit
= 0; /* check if contains 8bit data */
1390 int checknul
= 0, containsnul
= 0; /* check if contains NULs */
1391 int checklinelen
= 0, linelen
= 0; /* check for long lines */
1392 int checkllinelen
= 0; /* check for extra-long lines */
1393 int checkboundary
= 0, boundaryclash
= 0; /* check if clashes with multipart boundary */
1394 int checklinespace
= 0, linespace
= 0; /* check if any line ends with space */
1399 struct text
*t
= NULL
;
1401 CE ce
= &ct
->c_cefile
;
1404 * handle multipart by scanning all subparts
1405 * and then checking their encoding.
1407 if (ct
->c_type
== CT_MULTIPART
) {
1408 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1411 /* initially mark the domain of enclosing multipart as 7bit */
1412 ct
->c_encoding
= CE_7BIT
;
1414 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1415 CT p
= part
->mp_part
;
1417 if (scan_content (p
, maxunencoded
) == NOTOK
) /* choose encoding for subpart */
1420 /* if necessary, enlarge encoding for enclosing multipart */
1421 if (p
->c_encoding
== CE_BINARY
)
1422 ct
->c_encoding
= CE_BINARY
;
1423 if (p
->c_encoding
== CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
)
1424 ct
->c_encoding
= CE_8BIT
;
1431 * Decide what to check while scanning this content. Note that
1432 * for text content we always check for 8bit characters if the
1433 * charset is unspecified, because that controls whether or not the
1434 * character set is us-ascii or retrieved from the locale. And
1435 * we check even if the charset is specified, to allow setting
1436 * the proper Content-Transfer-Encoding.
1439 if (ct
->c_type
== CT_TEXT
) {
1440 t
= (struct text
*) ct
->c_ctparams
;
1441 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
1447 switch (ct
->c_reqencoding
) {
1458 /* Use the default rules based on content-type */
1459 switch (ct
->c_type
) {
1463 if (ct
->c_subtype
== TEXT_PLAIN
) {
1470 case CT_APPLICATION
:
1482 /* don't check anything for message/external */
1483 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1496 * Don't check anything for these types,
1497 * since we are forcing use of base64, unless
1498 * the content-type was specified by a mhbuild directive.
1509 * Scan the unencoded content
1511 if (check8bit
|| checklinelen
|| checklinespace
|| checkboundary
||
1512 checkllinelen
|| checknul
) {
1513 if ((in
= fopen (ce
->ce_file
, "r")) == NULL
)
1514 adios (ce
->ce_file
, "unable to open for reading");
1515 prefix_len
= strlen (prefix
);
1517 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1519 * Check for 8bit and NUL data.
1521 for (cp
= bufp
; (check8bit
|| checknul
) &&
1522 cp
< bufp
+ gotlen
; cp
++) {
1523 if (!isascii ((unsigned char) *cp
)) {
1525 check8bit
= 0; /* no need to keep checking */
1529 checknul
= 0; /* no need to keep checking */
1534 * Check line length.
1536 if (checklinelen
&& ((size_t)gotlen
> maxunencoded
+ 1)) {
1538 checklinelen
= 0; /* no need to keep checking */
1542 * RFC 5322 specifies that a message cannot contain a line
1543 * greater than 998 characters (excluding the CRLF). If we
1544 * get one of those lines and linelen is NOT set, then abort.
1547 if (checkllinelen
&& !linelen
&&
1548 (gotlen
> MAXLONGLINE
+ 1)) {
1549 adios(NULL
, "Line in content exceeds maximum line limit (%d)",
1554 * Check if line ends with a space.
1556 if (checklinespace
&& (cp
= bufp
+ gotlen
- 2) > bufp
&&
1557 isspace ((unsigned char) *cp
)) {
1559 checklinespace
= 0; /* no need to keep checking */
1563 * Check if content contains a line that clashes
1564 * with our standard boundary for multipart messages.
1566 if (checkboundary
&& bufp
[0] == '-' && bufp
[1] == '-') {
1567 for (cp
= bufp
+ gotlen
- 1; cp
>= bufp
; cp
--)
1568 if (!isspace ((unsigned char) *cp
))
1571 if (!strncmp(bufp
+ 2, prefix
, prefix_len
) &&
1572 isdigit((unsigned char) bufp
[2 + prefix_len
])) {
1574 checkboundary
= 0; /* no need to keep checking */
1583 * If the content is text and didn't specify a character set,
1584 * we need to figure out which one was used.
1586 set_charset (ct
, contains8bit
);
1589 * Decide which transfer encoding to use.
1592 if (ct
->c_reqencoding
!= CE_UNKNOWN
)
1593 ct
->c_encoding
= ct
->c_reqencoding
;
1595 int wants_q_p
= (containsnul
|| linelen
|| linespace
|| checksw
);
1597 switch (ct
->c_type
) {
1600 ct
->c_encoding
= CE_QUOTED
;
1601 else if (contains8bit
)
1602 ct
->c_encoding
= CE_8BIT
;
1604 ct
->c_encoding
= CE_7BIT
;
1608 case CT_APPLICATION
:
1609 /* For application type, use base64, except when postscript */
1610 if (wants_q_p
|| contains8bit
) {
1611 if (ct
->c_subtype
== APPLICATION_POSTSCRIPT
)
1612 ct
->c_encoding
= CE_QUOTED
; /* historical */
1614 ct
->c_encoding
= CE_BASE64
;
1616 ct
->c_encoding
= CE_7BIT
;
1621 ct
->c_encoding
= contains8bit
? CE_8BIT
: CE_7BIT
;
1627 /* For audio, image, and video contents, just use base64 */
1628 ct
->c_encoding
= CE_BASE64
;
1633 return (boundaryclash
? NOTOK
: OK
);
1638 * Scan the content structures, and build header
1639 * fields that will need to be output into the
1644 build_headers (CT ct
, int header_encoding
)
1646 int cc
, mailbody
, extbody
, len
;
1647 char *np
, *vp
, buffer
[BUFSIZ
];
1648 CI ci
= &ct
->c_ctinfo
;
1651 * If message is type multipart, then add the multipart
1652 * boundary to the list of attribute/value pairs.
1654 if (ct
->c_type
== CT_MULTIPART
) {
1655 static int level
= 0; /* store nesting level */
1657 snprintf (buffer
, sizeof(buffer
), "%s%d", prefix
, level
++);
1658 add_param(&ci
->ci_first_pm
, &ci
->ci_last_pm
, "boundary", buffer
, 0);
1662 * Skip the output of Content-Type, parameters, content
1663 * description and disposition, and Content-ID if the
1664 * content is of type "message" and the rfc934 compatibility
1665 * flag is set (which means we are inside multipart/digest
1666 * and the switch -rfc934mode was given).
1668 if (ct
->c_type
== CT_MESSAGE
&& ct
->c_rfc934
)
1672 * output the content type and subtype
1674 np
= mh_xstrdup(TYPE_FIELD
);
1675 vp
= concat (" ", ci
->ci_type
, "/", ci
->ci_subtype
, NULL
);
1677 /* keep track of length of line */
1678 len
= strlen (TYPE_FIELD
) + strlen (ci
->ci_type
)
1679 + strlen (ci
->ci_subtype
) + 3;
1681 extbody
= ct
->c_type
== CT_MESSAGE
&& ct
->c_subtype
== MESSAGE_EXTERNAL
;
1682 mailbody
= extbody
&& ((struct exbody
*) ct
->c_ctparams
)->eb_body
;
1685 * Append the attribute/value pairs to
1686 * the end of the Content-Type line.
1689 if (ci
->ci_first_pm
) {
1690 char *s
= output_params(len
, ci
->ci_first_pm
, &len
, mailbody
);
1693 adios(NULL
, "Internal error: failed outputting Content-Type "
1701 * Append any RFC-822 comment to the end of
1702 * the Content-Type line.
1704 if (ci
->ci_comment
) {
1705 snprintf (buffer
, sizeof(buffer
), "(%s)", ci
->ci_comment
);
1706 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
1707 vp
= add ("\n\t", vp
);
1713 vp
= add (buffer
, vp
);
1716 vp
= add ("\n", vp
);
1717 add_header (ct
, np
, vp
);
1720 * output the Content-ID, unless disabled by -nocontentid. Note that
1721 * RFC 2045 always requires a Content-ID header for message/external-body
1724 if ((contentidsw
|| ct
->c_ctexbody
) && ct
->c_id
) {
1725 np
= mh_xstrdup(ID_FIELD
);
1726 vp
= concat (" ", ct
->c_id
, NULL
);
1727 add_header (ct
, np
, vp
);
1730 * output the Content-Description
1733 np
= mh_xstrdup(DESCR_FIELD
);
1734 vp
= concat (" ", ct
->c_descr
, NULL
);
1735 if (header_encoding
!= CE_8BIT
) {
1736 if (encode_rfc2047(DESCR_FIELD
, &vp
, header_encoding
, NULL
)) {
1737 adios(NULL
, "Unable to encode %s header", DESCR_FIELD
);
1740 add_header (ct
, np
, vp
);
1744 * output the Content-Disposition. If it's NULL but c_dispo_type is
1745 * set, then we need to build it.
1748 np
= mh_xstrdup(DISPO_FIELD
);
1749 vp
= concat (" ", ct
->c_dispo
, NULL
);
1750 add_header (ct
, np
, vp
);
1751 } else if (ct
->c_dispo_type
) {
1752 vp
= concat (" ", ct
->c_dispo_type
, NULL
);
1753 len
= strlen(DISPO_FIELD
) + strlen(vp
) + 1;
1754 np
= output_params(len
, ct
->c_dispo_first
, NULL
, 0);
1758 add_header (ct
, mh_xstrdup(DISPO_FIELD
), vp
);
1763 * If this is the internal content structure for a
1764 * "message/external", then we are done with the
1765 * headers (since it has no body).
1771 * output the Content-MD5
1774 np
= mh_xstrdup(MD5_FIELD
);
1775 vp
= calculate_digest (ct
, ct
->c_encoding
== CE_QUOTED
);
1776 add_header (ct
, np
, vp
);
1780 * output the Content-Transfer-Encoding
1781 * If using EAI and message body is 7-bit, force 8-bit C-T-E.
1783 if (header_encoding
== CE_8BIT
&& ct
->c_encoding
== CE_7BIT
) {
1784 ct
->c_encoding
= CE_8BIT
;
1787 switch (ct
->c_encoding
) {
1789 /* Nothing to output */
1793 np
= mh_xstrdup(ENCODING_FIELD
);
1794 vp
= concat (" ", "8bit", "\n", NULL
);
1795 add_header (ct
, np
, vp
);
1799 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1800 adios (NULL
, "internal error, invalid encoding");
1802 np
= mh_xstrdup(ENCODING_FIELD
);
1803 vp
= concat (" ", "quoted-printable", "\n", NULL
);
1804 add_header (ct
, np
, vp
);
1808 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1809 adios (NULL
, "internal error, invalid encoding");
1811 np
= mh_xstrdup(ENCODING_FIELD
);
1812 vp
= concat (" ", "base64", "\n", NULL
);
1813 add_header (ct
, np
, vp
);
1817 if (ct
->c_type
== CT_MESSAGE
)
1818 adios (NULL
, "internal error, invalid encoding");
1820 np
= mh_xstrdup(ENCODING_FIELD
);
1821 vp
= concat (" ", "binary", "\n", NULL
);
1822 add_header (ct
, np
, vp
);
1826 adios (NULL
, "unknown transfer encoding in content");
1831 * Additional content specific header processing
1833 switch (ct
->c_type
) {
1836 struct multipart
*m
;
1839 m
= (struct multipart
*) ct
->c_ctparams
;
1840 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1844 build_headers (p
, header_encoding
);
1850 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1853 e
= (struct exbody
*) ct
->c_ctparams
;
1854 build_headers (e
->eb_content
, header_encoding
);
1867 static char nib2b64
[0x40+1] =
1868 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1871 calculate_digest (CT ct
, int asciiP
)
1876 unsigned char digest
[16];
1877 unsigned char outbuf
[25];
1879 CE ce
= &ct
->c_cefile
;
1880 char *infilename
= ce
->ce_file
? ce
->ce_file
: ct
->c_file
;
1884 if ((in
= fopen (infilename
, "r")) == NULL
)
1885 adios (infilename
, "unable to open for reading");
1887 /* Initialize md5 context */
1888 MD5Init (&mdContext
);
1890 /* calculate md5 message digest */
1895 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1898 cp
= bufp
+ gotlen
- 1;
1899 if ((c
= *cp
) == '\n')
1902 MD5Update (&mdContext
, (unsigned char *) bufp
,
1903 (unsigned int) gotlen
);
1906 MD5Update (&mdContext
, (unsigned char *) "\r\n", 2);
1909 char buffer
[BUFSIZ
];
1910 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), in
)) > 0)
1911 MD5Update (&mdContext
, (unsigned char *) buffer
, (unsigned int) cc
);
1914 /* md5 finalization. Write digest and zero md5 context */
1915 MD5Final (digest
, &mdContext
);
1920 /* print debugging info */
1924 fprintf (stderr
, "MD5 digest=");
1925 for (ep
= (dp
= digest
) + sizeof(digest
) / sizeof(digest
[0]);
1927 fprintf (stderr
, "%02x", *dp
& 0xff);
1928 fprintf (stderr
, "\n");
1931 /* encode the digest using base64 */
1932 for (dp
= digest
, op
= (char *) outbuf
,
1933 cc
= sizeof(digest
) / sizeof(digest
[0]);
1934 cc
> 0; cc
-= 3, op
+= 4) {
1938 bits
= (*dp
++ & 0xff) << 16;
1940 bits
|= (*dp
++ & 0xff) << 8;
1942 bits
|= *dp
++ & 0xff;
1945 for (bp
= op
+ 4; bp
> op
; bits
>>= 6)
1946 *--bp
= nib2b64
[bits
& 0x3f];
1954 /* null terminate string */
1957 /* now make copy and return string */
1958 vp
= concat (" ", outbuf
, "\n", NULL
);
1963 * Set things up for the content structure for file "filename" that
1968 setup_attach_content(CT ct
, char *filename
)
1970 char *type
, *simplename
= r1bindex(filename
, '/');
1971 struct str2init
*s2i
;
1974 if (! (type
= mime_type(filename
))) {
1975 adios(NULL
, "Unable to determine MIME type of \"%s\"", filename
);
1979 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1980 * since we're just feeding it a MIME type we have to add those ourselves.
1981 * Map that to a valid content-type label and call any initialization
1985 if (get_ctinfo(type
, ct
, 0) == NOTOK
)
1990 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
1991 if (strcasecmp(ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0)
1993 if (!s2i
->si_key
&& !uprf(ct
->c_ctinfo
.ci_type
, "X-"))
1997 * Make sure the type isn't incompatible with what we can handle
2000 switch (ct
->c_type
= s2i
->si_val
) {
2002 adios (NULL
, "multipart types must be specified by mhbuild directives");
2006 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "partial") == 0)
2007 adios(NULL
, "Sorry, %s/%s isn't supported", ct
->c_ctinfo
.ci_type
,
2008 ct
->c_ctinfo
.ci_subtype
);
2009 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "external-body") == 0)
2010 adios(NULL
, "external-body messages must be specified "
2011 "by mhbuild directives");
2016 * This sets the subtype, if it's significant
2018 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
2019 (*ct
->c_ctinitfnx
)(ct
);
2024 * Feed in a few attributes; specifically, the name attribute, the
2025 * content-description, and the content-disposition.
2028 for (pm
= ct
->c_ctinfo
.ci_first_pm
; pm
; pm
= pm
->pm_next
) {
2029 if (strcasecmp(pm
->pm_name
, "name") == 0) {
2030 mh_xfree(pm
->pm_value
);
2031 pm
->pm_value
= mh_xstrdup(simplename
);
2037 add_param(&ct
->c_ctinfo
.ci_first_pm
, &ct
->c_ctinfo
.ci_last_pm
,
2038 "name", simplename
, 0);
2040 ct
->c_descr
= mh_xstrdup(simplename
);
2041 ct
->c_descr
= add("\n", ct
->c_descr
);
2042 ct
->c_cefile
.ce_file
= mh_xstrdup(filename
);
2044 set_disposition (ct
);
2046 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename", simplename
, 0);
2050 * If disposition type hasn't already been set in ct:
2051 * Look for mhbuild-disposition-<type>/<subtype> entry
2052 * that specifies Content-Disposition type. Only
2053 * 'attachment' and 'inline' are allowed. Default to
2057 set_disposition (CT ct
) {
2058 if (ct
->c_dispo_type
== NULL
) {
2059 char *cp
= context_find_by_type ("disposition", ct
->c_ctinfo
.ci_type
,
2060 ct
->c_ctinfo
.ci_subtype
);
2062 if (cp
&& strcasecmp (cp
, "attachment") &&
2063 strcasecmp (cp
, "inline")) {
2064 inform("configuration problem: %s-disposition-%s%s%s specifies "
2065 "'%s' but only 'attachment' and 'inline' are allowed, "
2066 "continuing...", invo_name
,
2067 ct
->c_ctinfo
.ci_type
,
2068 ct
->c_ctinfo
.ci_subtype
? "/" : "",
2069 ct
->c_ctinfo
.ci_subtype
? ct
->c_ctinfo
.ci_subtype
: "",
2075 ct
->c_dispo_type
= mh_xstrdup(cp
);
2080 * Set text content charset if it was unspecified. contains8bit
2082 * 0: content does not contain 8-bit characters
2083 * 1: content contains 8-bit characters
2084 * -1: ignore content and use user's locale to determine charset
2087 set_charset (CT ct
, int contains8bit
) {
2088 if (ct
->c_type
== CT_TEXT
) {
2091 if (ct
->c_ctparams
== NULL
) {
2094 t
->tx_charset
= CHARSET_UNSPECIFIED
;
2096 t
= (struct text
*) ct
->c_ctparams
;
2099 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
2100 CI ci
= &ct
->c_ctinfo
;
2101 char *eightbitcharset
= write_charset_8bit();
2102 char *charset
= contains8bit
? eightbitcharset
: "us-ascii";
2104 if (contains8bit
== 1 &&
2105 strcasecmp (eightbitcharset
, "US-ASCII") == 0) {
2106 adios (NULL
, "Text content contains 8 bit characters, but "
2107 "character set is US-ASCII");
2110 add_param (&ci
->ci_first_pm
, &ci
->ci_last_pm
, "charset", charset
,
2113 t
->tx_charset
= CHARSET_SPECIFIED
;
2120 * Look at all of the replied-to message parts and expand any that
2121 * are matched by a pseudoheader. Except don't descend into
2125 expand_pseudoheaders (CT ct
, struct multipart
*m
, const char *infile
,
2126 const convert_list
*convert_head
) {
2127 /* text_plain_ct is used to concatenate all of the text/plain
2128 replies into one part, instead of having each one in a separate
2130 CT text_plain_ct
= NULL
;
2132 switch (ct
->c_type
) {
2133 case CT_MULTIPART
: {
2134 struct multipart
*mp
= (struct multipart
*) ct
->c_ctparams
;
2137 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
2140 /* The parts are in descending priority order (defined by
2141 RFC 2046 Sec. 5.1.4) because they were reversed by
2142 parse_mime (). So, stop looking for matches with
2143 immediate subparts after the first match of an
2145 for (part
= mp
->mp_parts
; ! matched
&& part
; part
= part
->mp_next
) {
2146 char *type_subtype
=
2147 concat (part
->mp_part
->c_ctinfo
.ci_type
, "/",
2148 part
->mp_part
->c_ctinfo
.ci_subtype
, NULL
);
2150 if (part
->mp_part
->c_type
== CT_MULTIPART
) {
2151 expand_pseudoheaders (part
->mp_part
, m
, infile
,
2154 const convert_list
*c
;
2156 for (c
= convert_head
; c
; c
= c
->next
) {
2157 if (! strcasecmp (type_subtype
, c
->type
)) {
2158 expand_pseudoheader (part
->mp_part
, &text_plain_ct
,
2160 c
->type
, c
->argstring
);
2166 free (type_subtype
);
2169 for (part
= mp
->mp_parts
; part
; part
= part
->mp_next
) {
2170 expand_pseudoheaders (part
->mp_part
, m
, infile
, convert_head
);
2177 char *type_subtype
=
2178 concat (ct
->c_ctinfo
.ci_type
, "/", ct
->c_ctinfo
.ci_subtype
,
2180 const convert_list
*c
;
2182 for (c
= convert_head
; c
; c
= c
->next
) {
2183 if (! strcasecmp (type_subtype
, c
->type
)) {
2184 expand_pseudoheader (ct
, &text_plain_ct
, m
, infile
, c
->type
,
2189 free (type_subtype
);
2197 * Expand a single pseudoheader. It's for the specified type.
2200 expand_pseudoheader (CT ct
, CT
*text_plain_ct
, struct multipart
*m
,
2201 const char *infile
, const char *type
,
2202 const char *argstring
) {
2204 FILE *reply_fp
= NULL
;
2205 char *convert
, *type_p
, *subtype_p
;
2206 char *convert_command
;
2207 char *charset
= NULL
;
2209 struct str2init
*s2i
;
2215 type_p
= getcpy (type
);
2216 if ((subtype_p
= strchr (type_p
, '/'))) {
2217 *subtype_p
++ = '\0';
2218 convert
= context_find_by_type ("convert", type_p
, subtype_p
);
2221 type_p
= concat ("mhbuild-convert-", type
, NULL
);
2222 convert
= context_find (type_p
);
2227 /* No mhbuild-convert- entry in mhn.defaults or profile
2231 /* reply_file is used to pass the output of the convert. */
2232 reply_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2234 concat (convert
, " ", argstring
? argstring
: "", " >", reply_file
,
2237 /* Convert here . . . */
2238 ct
->c_storeproc
= mh_xstrdup(convert_command
);
2239 ct
->c_umask
= ~m_gmprot ();
2241 if ((status
= show_content_aux (ct
, 0, convert_command
, NULL
, NULL
)) !=
2243 inform("store of %s content failed, continuing...", type
);
2245 free (convert_command
);
2247 /* Fill out the the new ct, reply_ct. */
2249 init_decoded_content (reply_ct
, infile
);
2251 if (extract_headers (reply_ct
, reply_file
, &reply_fp
) == NOTOK
) {
2253 inform("failed to extract headers from convert output in %s, "
2254 "continuing...", reply_file
);
2258 /* For text content only, see if it is 8-bit text. */
2259 if (reply_ct
->c_type
== CT_TEXT
) {
2262 if ((fd
= open (reply_file
, O_RDONLY
)) == NOTOK
||
2263 scan_input (fd
, &eightbit
) == NOTOK
) {
2265 inform("failed to read %s, continuing...", reply_file
);
2271 /* This sets reply_ct->c_ctparams, and reply_ct->c_termproc if the
2272 charset can't be handled natively. */
2273 for (s2i
= str2cts
; s2i
->si_key
; s2i
++) {
2274 if (strcasecmp(reply_ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0) {
2279 if ((reply_ct
->c_ctinitfnx
= s2i
->si_init
)) {
2280 (*reply_ct
->c_ctinitfnx
)(reply_ct
);
2284 get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1))) {
2285 /* The reply Content-Type had the charset. */
2288 set_charset (reply_ct
, -1);
2289 charset
= get_param (reply_ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 1);
2290 if (reply_ct
->c_reqencoding
== CE_UNKNOWN
&&
2291 reply_ct
->c_type
== CT_TEXT
) {
2292 /* Assume that 8bit is sufficient (for text). In other words,
2293 don't allow it to be encoded as quoted printable if lines
2294 are too long. This also sidesteps the check for whether
2295 it needs to be encoded as binary; instead, it relies on
2296 the applicable mhbuild-convert-text directive to ensure
2297 that the resultant text is not binary. */
2298 reply_ct
->c_reqencoding
= eightbit
? CE_8BIT
: CE_7BIT
;
2302 /* Concatenate text/plain parts. */
2303 if (reply_ct
->c_type
== CT_TEXT
&&
2304 reply_ct
->c_subtype
== TEXT_PLAIN
) {
2305 if (! *text_plain_ct
&& m
->mp_parts
&& m
->mp_parts
->mp_part
&&
2306 m
->mp_parts
->mp_part
->c_type
== CT_TEXT
&&
2307 m
->mp_parts
->mp_part
->c_subtype
== TEXT_PLAIN
) {
2308 *text_plain_ct
= m
->mp_parts
->mp_part
;
2309 /* Make sure that the charset is set in the text/plain
2311 set_charset (*text_plain_ct
, -1);
2312 if ((*text_plain_ct
)->c_reqencoding
== CE_UNKNOWN
) {
2313 /* Assume that 8bit is sufficient (for text). In other words,
2314 don't allow it to be encoded as quoted printable if lines
2315 are too long. This also sidesteps the check for whether
2316 it needs to be encoded as binary; instead, it relies on
2317 the applicable mhbuild-convert-text directive to ensure
2318 that the resultant text is not binary. */
2319 (*text_plain_ct
)->c_reqencoding
=
2320 eightbit
? CE_8BIT
: CE_7BIT
;
2324 if (*text_plain_ct
) {
2325 /* Only concatenate if the charsets are identical. */
2326 char *text_plain_ct_charset
=
2327 get_param ((*text_plain_ct
)->c_ctinfo
.ci_first_pm
, "charset",
2330 if (strcasecmp (text_plain_ct_charset
, charset
) == 0) {
2331 /* Append this text/plain reply to the first one.
2332 If there's a problem anywhere along the way,
2333 instead attach it is a separate part. */
2334 int text_plain_reply
=
2335 open ((*text_plain_ct
)->c_cefile
.ce_file
,
2336 O_WRONLY
| O_APPEND
);
2337 int addl_reply
= open (reply_file
, O_RDONLY
);
2339 if (text_plain_reply
!= NOTOK
&& addl_reply
!= NOTOK
) {
2340 /* Insert blank line before each addl part. */
2341 /* It would be nice not to do this for the first one. */
2342 if (write (text_plain_reply
, "\n", 1) == 1) {
2343 /* Copy the text from the new reply and
2344 then free its Content struct. */
2345 cpydata (addl_reply
, text_plain_reply
,
2346 (*text_plain_ct
)->c_cefile
.ce_file
,
2348 if (close (text_plain_reply
) == OK
&&
2349 close (addl_reply
) == OK
) {
2350 /* If appended text needed 8-bit but first text didn't,
2351 propagate the 8-bit indication. */
2352 if ((*text_plain_ct
)->c_reqencoding
== CE_7BIT
&&
2353 reply_ct
->c_reqencoding
== CE_8BIT
) {
2354 (*text_plain_ct
)->c_reqencoding
= CE_8BIT
;
2357 if (reply_fp
) { fclose (reply_fp
); }
2359 free_content (reply_ct
);
2366 *text_plain_ct
= reply_ct
;
2370 reply_ct
->c_cefile
.ce_file
= reply_file
;
2371 reply_ct
->c_cefile
.ce_fp
= reply_fp
;
2372 reply_ct
->c_cefile
.ce_unlink
= 1;
2374 /* Attach the new part to the parent multipart/mixed, "m". */
2376 part
->mp_part
= reply_ct
;
2380 for (p
= m
->mp_parts
; p
&& p
->mp_next
; p
= p
->mp_next
) { continue; }
2388 /* Extract any Content-Type header from beginning of convert output. */
2390 extract_headers (CT ct
, char *reply_file
, FILE **reply_fp
) {
2391 char *buffer
= NULL
, *cp
, *end_of_header
;
2392 int found_header
= 0;
2393 struct stat statbuf
;
2395 /* Read the convert reply from the file to memory. */
2396 if (stat (reply_file
, &statbuf
) == NOTOK
) {
2397 admonish (reply_file
, "failed to stat");
2398 goto failed_to_extract_ct
;
2401 buffer
= mh_xmalloc (statbuf
.st_size
+ 1);
2403 if ((*reply_fp
= fopen (reply_file
, "r+")) == NULL
||
2404 fread (buffer
, 1, (size_t) statbuf
.st_size
, *reply_fp
) <
2405 (size_t) statbuf
.st_size
) {
2406 admonish (reply_file
, "failed to read");
2407 goto failed_to_extract_ct
;
2409 buffer
[statbuf
.st_size
] = '\0';
2411 /* Look for a header in the convert reply. */
2412 if (strncasecmp (buffer
, TYPE_FIELD
, strlen (TYPE_FIELD
)) == 0 &&
2413 buffer
[strlen (TYPE_FIELD
)] == ':') {
2414 if ((end_of_header
= strstr (buffer
, "\r\n\r\n"))) {
2417 } else if ((end_of_header
= strstr (buffer
, "\n\n"))) {
2429 /* Truncate buffer to just the C-T. */
2430 *end_of_header
= '\0';
2431 n
= strlen (buffer
);
2433 if (get_ctinfo (buffer
+ 14, ct
, 0) != OK
) {
2434 inform("unable to get content info for reply, continuing...");
2435 goto failed_to_extract_ct
;
2438 /* Hack. Use parse_mime() to detect the type/subtype of the
2439 reply, which we'll use below. */
2440 tmp_file
= getcpy (m_mktemp2 (NULL
, invo_name
, NULL
, NULL
));
2441 if ((tmp_f
= fopen (tmp_file
, "w")) &&
2442 fwrite (buffer
, 1, n
, tmp_f
) == n
) {
2445 goto failed_to_extract_ct
;
2447 tmp_ct
= parse_mime (tmp_file
);
2450 /* The type and subtype were detected from the reply
2451 using parse_mime() above. */
2452 ct
->c_type
= tmp_ct
->c_type
;
2453 ct
->c_subtype
= tmp_ct
->c_subtype
;
2454 free_content (tmp_ct
);
2459 /* Rewrite the content without the header. */
2460 cp
= end_of_header
+ 1;
2463 if (fwrite (cp
, 1, statbuf
.st_size
- (cp
- buffer
), *reply_fp
) <
2464 (size_t) (statbuf
.st_size
- (cp
- buffer
))) {
2465 admonish (reply_file
, "failed to write");
2466 goto failed_to_extract_ct
;
2469 if (ftruncate (fileno (*reply_fp
), statbuf
.st_size
- (cp
- buffer
)) !=
2471 advise (reply_file
, "ftruncate");
2472 goto failed_to_extract_ct
;
2475 /* No header section, assume the reply is text/plain. */
2476 ct
->c_type
= CT_TEXT
;
2477 ct
->c_subtype
= TEXT_PLAIN
;
2478 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
) {
2479 /* This never should fail, but just in case. */
2480 adios (NULL
, "unable to get content info for reply");
2484 /* free_encoding() will close reply_fp, which is passed through
2485 ct->c_cefile.ce_fp. */
2489 failed_to_extract_ct
:
2490 if (*reply_fp
) { fclose (*reply_fp
); }