]>
diplodocus.org Git - nmh/blob - uip/mhbuildsbr.c
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.
20 #include <h/signals.h>
25 #include <h/mhparse.h>
28 #ifdef HAVE_SYS_TIME_H
29 # include <sys/time.h>
38 extern int contentidsw
;
41 extern int rcachesw
; /* mhcachesbr.c */
42 extern int wcachesw
; /* mhcachesbr.c */
44 static char prefix
[] = "----- =_aaaaaaaaaa";
48 struct attach_list
*next
;
52 * Maximum size of URL token in message/external-body
55 #define MAXURLTOKEN 40
59 void content_error (char *, CT
, char *, ...);
62 int find_cache (CT
, int, int *, char *, char *, int);
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 char *fgetstr (char *, int, FILE *);
74 static int user_content (FILE *, char *, CT
*, const char *infilename
);
75 static void set_id (CT
, int);
76 static int compose_content (CT
, int);
77 static int scan_content (CT
, size_t);
78 static int build_headers (CT
, int);
79 static char *calculate_digest (CT
, int);
82 static unsigned char directives_stack
[32];
83 static unsigned int directives_index
;
85 static int do_direct(void)
87 return directives_stack
[directives_index
];
90 static void directive_onoff(int onoff
)
92 if (directives_index
>= sizeof(directives_stack
) - 1) {
93 fprintf(stderr
, "mhbuild: #on/off overflow, continuing\n");
96 directives_stack
[++directives_index
] = onoff
;
99 static void directive_init(int onoff
)
101 directives_index
= 0;
102 directives_stack
[0] = onoff
;
105 static void directive_pop(void)
107 if (directives_index
> 0)
110 fprintf(stderr
, "mhbuild: #pop underflow, continuing\n");
114 * Main routine for translating composition file
115 * into valid MIME message. It translates the draft
116 * into a content structure (actually a tree of content
117 * structures). This message then can be manipulated
118 * in various ways, including being output via
123 build_mime (char *infile
, int autobuild
, int dist
, int directives
,
124 int header_encoding
, size_t maxunencoded
, int verbose
)
127 char buf
[BUFSIZ
], name
[NAMESZ
];
134 m_getfld_state_t gstate
= 0;
135 struct attach_list
*attach_head
= NULL
, *attach_tail
= NULL
, *at_entry
;
137 directive_init(directives
);
139 umask (~m_gmprot ());
141 /* open the composition draft */
142 if ((in
= fopen (infile
, "r")) == NULL
)
143 adios (infile
, "unable to open for reading");
146 * Allocate space for primary (outside) content
148 if ((ct
= (CT
) mh_xcalloc (1, sizeof(*ct
))) == NULL
)
149 adios (NULL
, "out of memory");
152 * Allocate structure for handling decoded content
153 * for this part. We don't really need this, but
154 * allocate it to remain consistent.
156 init_decoded_content (ct
, infile
);
159 * Parse some of the header fields in the composition
160 * draft into the linked list of header fields for
161 * the new MIME message.
163 m_getfld_track_filepos (&gstate
, in
);
164 for (compnum
= 1;;) {
165 int bufsz
= sizeof buf
;
166 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
171 /* abort if draft has Mime-Version or C-T-E header field */
172 if (strcasecmp (name
, VRSN_FIELD
) == 0 ||
173 strcasecmp (name
, ENCODING_FIELD
) == 0) {
179 adios (NULL
, "draft shouldn't contain %s: field", name
);
183 /* ignore any Content-Type fields in the header */
184 if (!strcasecmp (name
, TYPE_FIELD
)) {
185 while (state
== FLDPLUS
) {
187 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
192 /* get copies of the buffers */
193 np
= add (name
, NULL
);
194 vp
= add (buf
, NULL
);
196 /* if necessary, get rest of field */
197 while (state
== FLDPLUS
) {
199 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
200 vp
= add (buf
, vp
); /* add to previous value */
204 * Now add the header data to the list, unless it's an attach
205 * header; in that case, add it to our attach list
208 if (strcasecmp(ATTACH_FIELD
, np
) == 0 ||
209 strcasecmp(ATTACH_FIELD_ALT
, np
) == 0) {
210 struct attach_list
*entry
;
211 char *s
= vp
, *e
= vp
+ strlen(vp
) - 1;
215 * Make sure we can find the start of this filename.
216 * If it's blank, we skip completely. Otherwise, strip
217 * off any leading spaces and trailing newlines.
220 while (isspace((unsigned char) *s
))
223 while (e
> s
&& *e
== '\n')
231 entry
= mh_xmalloc(sizeof(*entry
));
232 entry
->filename
= getcpy(s
);
237 attach_tail
->next
= entry
;
240 attach_head
= attach_tail
= entry
;
243 add_header (ct
, np
, vp
);
247 /* if this wasn't the last header field, then continue */
251 fseek (in
, (long) (-strlen (buf
)), SEEK_CUR
);
258 adios (NULL
, "message format error in component #%d", compnum
);
261 adios (NULL
, "getfld() returned %d", state
);
265 m_getfld_state_destroy (&gstate
);
268 * Iterate through the list of headers and call the function to MIME-ify
272 for (hp
= ct
->c_first_hf
; hp
!= NULL
; hp
= hp
->next
) {
273 if (encode_rfc2047(hp
->name
, &hp
->value
, header_encoding
, NULL
)) {
274 adios(NULL
, "Unable to encode header \"%s\"", hp
->name
);
279 * Now add the MIME-Version header field
280 * to the list of header fields.
284 np
= add (VRSN_FIELD
, NULL
);
285 vp
= concat (" ", VRSN_VALUE
, "\n", NULL
);
286 add_header (ct
, np
, vp
);
290 * We initally assume we will find multiple contents in the
291 * draft. So create a multipart/mixed content to hold everything.
292 * We can remove this later, if it is not needed.
294 if (get_ctinfo ("multipart/mixed", ct
, 0) == NOTOK
)
296 ct
->c_type
= CT_MULTIPART
;
297 ct
->c_subtype
= MULTI_MIXED
;
299 if ((m
= (struct multipart
*) mh_xcalloc (1, sizeof(*m
))) == NULL
)
300 adios (NULL
, "out of memory");
301 ct
->c_ctparams
= (void *) m
;
305 * read and parse the composition file
306 * and the directives it contains.
308 while (fgetstr (buf
, sizeof(buf
) - 1, in
)) {
312 if (user_content (in
, buf
, &p
, infile
) == DONE
) {
313 admonish (NULL
, "ignoring spurious #end");
319 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
320 adios (NULL
, "out of memory");
327 * Add any Attach headers to the list of MIME parts at the end of the
331 for (at_entry
= attach_head
; at_entry
; ) {
332 struct attach_list
*at_prev
= at_entry
;
336 if (access(at_entry
->filename
, R_OK
) != 0) {
337 adios("reading", "Unable to open %s for", at_entry
->filename
);
340 if ((p
= (CT
) mh_xcalloc (1, sizeof(*p
))) == NULL
)
341 adios(NULL
, "out of memory");
343 init_decoded_content(p
, infile
);
346 * Initialize our content structure based on the filename,
347 * and fill in all of the relevant fields. Also place MIME
348 * parameters in the attributes array.
351 setup_attach_content(p
, at_entry
->filename
);
353 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
354 adios (NULL
, "out of memory");
359 at_entry
= at_entry
->next
;
360 free(at_prev
->filename
);
365 * To allow for empty message bodies, if we've found NO content at all
366 * yet cook up an empty text/plain part.
374 if ((p
= (CT
) mh_xcalloc (1, sizeof(*p
))) == NULL
)
375 adios(NULL
, "out of memory");
377 init_decoded_content(p
, infile
);
379 if (get_ctinfo ("text/plain", p
, 0) == NOTOK
)
383 p
->c_subtype
= TEXT_PLAIN
;
384 p
->c_encoding
= CE_7BIT
;
386 * Sigh. ce_file contains the "decoded" contents of this part.
387 * So this seems like the best option available since we're going
388 * to call scan_content() on this.
390 p
->c_cefile
.ce_file
= getcpy("/dev/null");
391 p
->c_begin
= ftell(in
);
392 p
->c_end
= ftell(in
);
394 if ((t
= (struct text
*) mh_xcalloc (1, sizeof (*t
))) == NULL
)
395 adios (NULL
, "out of memory");
397 t
->tx_charset
= CHARSET_SPECIFIED
;
400 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
401 adios (NULL
, "out of memory");
407 * close the composition draft since
408 * it's not needed any longer.
413 * If only one content was found, then remove and
414 * free the outer multipart content.
416 if (!m
->mp_parts
->mp_next
) {
419 p
= m
->mp_parts
->mp_part
;
420 m
->mp_parts
->mp_part
= NULL
;
422 /* move header fields */
423 p
->c_first_hf
= ct
->c_first_hf
;
424 p
->c_last_hf
= ct
->c_last_hf
;
425 ct
->c_first_hf
= NULL
;
426 ct
->c_last_hf
= NULL
;
435 * Fill out, or expand directives. Parse and execute
436 * commands specified by profile composition strings.
438 compose_content (ct
, verbose
);
440 if ((cp
= strchr(prefix
, 'a')) == NULL
)
441 adios (NULL
, "internal error(4)");
444 * Scan the contents. Choose a transfer encoding, and
445 * check if prefix for multipart boundary clashes with
446 * any of the contents.
448 while (scan_content (ct
, maxunencoded
) == NOTOK
) {
453 adios (NULL
, "giving up trying to find a unique delimiter string");
459 /* Build the rest of the header field structures */
461 build_headers (ct
, header_encoding
);
468 * Set up structures for placing unencoded
469 * content when building parts.
473 init_decoded_content (CT ct
, const char *filename
)
475 ct
->c_ceopenfnx
= open7Bit
; /* since unencoded */
476 ct
->c_ceclosefnx
= close_encoding
;
477 ct
->c_cesizefnx
= NULL
; /* since unencoded */
478 ct
->c_encoding
= CE_7BIT
; /* Seems like a reasonable default */
479 ct
->c_file
= add(filename
, NULL
);
486 fgetstr (char *s
, int n
, FILE *stream
)
492 for (ep
= (cp
= s
) + o_n
; cp
< ep
; ) {
495 if (!fgets (cp
, n
, stream
))
496 return (cp
!= s
? s
: NULL
);
498 if (cp
== s
&& *cp
!= '#')
501 cp
+= (i
= strlen (cp
)) - 1;
502 if (i
<= 1 || *cp
-- != '\n' || *cp
!= '\\')
508 if (strcmp(s
, "#on\n") == 0) {
510 } else if (strcmp(s
, "#off\n") == 0) {
512 } else if (strcmp(s
, "#pop\n") == 0) {
524 * Parse the composition draft for text and directives.
525 * Do initial setup of Content structure.
529 user_content (FILE *in
, char *buf
, CT
*ctp
, const char *infilename
)
537 struct str2init
*s2i
;
542 if (buf
[0] == '\n' || (do_direct() && strcmp (buf
, "#\n") == 0)) {
547 /* allocate basic Content structure */
548 if ((ct
= (CT
) mh_xcalloc (1, sizeof(*ct
))) == NULL
)
549 adios (NULL
, "out of memory");
552 /* allocate basic structure for handling decoded content */
553 init_decoded_content (ct
, infilename
);
560 * Handle inline text. Check if line
561 * is one of the following forms:
563 * 1) doesn't begin with '#' (implicit directive)
564 * 2) begins with "##" (implicit directive)
565 * 3) begins with "#<"
567 if (!do_direct() || buf
[0] != '#' || buf
[1] == '#' || buf
[1] == '<') {
571 char content
[BUFSIZ
];
575 if ((cp
= m_mktemp2(NULL
, invo_name
, NULL
, &out
)) == NULL
) {
576 adios("mhbuildsbr", "unable to create temporary file in %s",
580 /* use a temp file to collect the plain text lines */
581 ce
->ce_file
= add (cp
, NULL
);
584 if (do_direct() && (buf
[0] == '#' && buf
[1] == '<')) {
585 strncpy (content
, buf
+ 2, sizeof(content
));
592 /* the directive is implicit */
593 strncpy (content
, "text/plain", sizeof(content
));
595 strncpy (buffer
, (!do_direct() || buf
[0] != '#') ? buf
: buf
+ 1, sizeof(buffer
));
599 if (headers
>= 0 && do_direct() && uprf (buffer
, DESCR_FIELD
)
600 && buffer
[i
= strlen (DESCR_FIELD
)] == ':') {
604 ct
->c_descr
= add (buffer
+ i
+ 1, ct
->c_descr
);
605 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
606 adios (NULL
, "end-of-file after %s: field in plaintext", DESCR_FIELD
);
614 adios (NULL
, "#-directive after %s: field in plaintext", DESCR_FIELD
);
622 if (headers
>= 0 && do_direct() && uprf (buffer
, DISPO_FIELD
)
623 && buffer
[i
= strlen (DISPO_FIELD
)] == ':') {
627 ct
->c_dispo
= add (buffer
+ i
+ 1, ct
->c_dispo
);
628 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
629 adios (NULL
, "end-of-file after %s: field in plaintext", DISPO_FIELD
);
637 adios (NULL
, "#-directive after %s: field in plaintext", DISPO_FIELD
);
645 if (headers
!= 1 || buffer
[0] != '\n')
651 if ((cp
= fgetstr (buffer
, sizeof(buffer
) - 1, in
)) == NULL
)
653 if (do_direct() && buffer
[0] == '#') {
656 if (buffer
[1] != '#')
658 for (cp
= (bp
= buffer
) + 1; *cp
; cp
++)
665 ct
->c_end
= ftell (out
);
668 /* parse content type */
669 if (get_ctinfo (content
, ct
, inlineD
) == NOTOK
)
672 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
673 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
675 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
679 * check type specified (possibly implicitly)
681 switch (ct
->c_type
= s2i
->si_val
) {
683 if (!strcasecmp (ci
->ci_subtype
, "rfc822")) {
684 ct
->c_encoding
= CE_7BIT
;
689 adios (NULL
, "it doesn't make sense to define an in-line %s content",
690 ct
->c_type
== CT_MESSAGE
? "message" : "multipart");
695 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
696 (*ct
->c_ctinitfnx
) (ct
);
701 fseek (in
, pos
, SEEK_SET
);
706 * If we've reached this point, the next line
707 * must be some type of explicit directive.
710 /* check if directive is external-type */
711 extrnal
= (buf
[1] == '@');
713 /* parse directive */
714 if (get_ctinfo (buf
+ (extrnal
? 2 : 1), ct
, 1) == NOTOK
)
717 /* check directive against the list of MIME types */
718 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
719 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
723 * Check if the directive specified a valid type.
724 * This will happen if it was one of the following forms:
731 adios (NULL
, "missing subtype in \"#%s\"", ci
->ci_type
);
733 switch (ct
->c_type
= s2i
->si_val
) {
735 adios (NULL
, "use \"#begin ... #end\" instead of \"#%s/%s\"",
736 ci
->ci_type
, ci
->ci_subtype
);
740 if (!strcasecmp (ci
->ci_subtype
, "partial"))
741 adios (NULL
, "sorry, \"#%s/%s\" isn't supported",
742 ci
->ci_type
, ci
->ci_subtype
);
743 if (!strcasecmp (ci
->ci_subtype
, "external-body"))
744 adios (NULL
, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
745 ci
->ci_type
, ci
->ci_subtype
);
748 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
749 ci
->ci_type
, ci
->ci_subtype
);
753 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
754 (*ct
->c_ctinitfnx
) (ct
);
759 * #@type/subtype (external types directive)
766 adios (NULL
, "need external information for \"#@%s/%s\"",
767 ci
->ci_type
, ci
->ci_subtype
);
770 snprintf (buffer
, sizeof(buffer
), "message/external-body; %s", ci
->ci_magic
);
775 * Since we are using the current Content structure to
776 * hold information about the type of the external
777 * reference, we need to create another Content structure
778 * for the message/external-body to wrap it in.
780 if ((ct
= (CT
) mh_xcalloc (1, sizeof(*ct
))) == NULL
)
781 adios (NULL
, "out of memory");
782 init_decoded_content(ct
, infilename
);
784 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
786 ct
->c_type
= CT_MESSAGE
;
787 ct
->c_subtype
= MESSAGE_EXTERNAL
;
789 if ((e
= (struct exbody
*) mh_xcalloc (1, sizeof(*e
))) == NULL
)
790 adios (NULL
, "out of memory");
791 ct
->c_ctparams
= (void *) e
;
797 if (params_external (ct
, 1) == NOTOK
)
803 /* Handle [file] argument */
805 /* check if specifies command to execute */
806 if (*ci
->ci_magic
== '|' || *ci
->ci_magic
== '!') {
807 for (cp
= ci
->ci_magic
+ 1; isspace ((unsigned char) *cp
); cp
++)
810 adios (NULL
, "empty pipe command for #%s directive", ci
->ci_type
);
815 /* record filename of decoded contents */
816 ce
->ce_file
= ci
->ci_magic
;
817 if (access (ce
->ce_file
, R_OK
) == NOTOK
)
818 adios ("reading", "unable to access %s for", ce
->ce_file
);
819 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
820 ct
->c_end
= (long) st
.st_size
;
827 * No [file] argument, so check profile for
828 * method to compose content.
830 cp
= context_find_by_type ("compose", ci
->ci_type
, ci
->ci_subtype
);
832 content_error (NULL
, ct
, "don't know how to compose content");
835 ci
->ci_magic
= add (cp
, NULL
);
840 adios (NULL
, "external definition not allowed for \"#%s\"", ci
->ci_type
);
844 * #forw [+folder] [msgs]
846 if (!strcasecmp (ci
->ci_type
, "forw")) {
848 char *folder
, *arguments
[MAXARGS
];
852 ap
= brkstring (ci
->ci_magic
, " ", "\n");
853 copyip (ap
, arguments
, MAXARGS
);
855 arguments
[0] = "cur";
860 /* search the arguments for a folder name */
861 for (ap
= arguments
; *ap
; ap
++) {
863 if (*cp
== '+' || *cp
== '@') {
865 adios (NULL
, "only one folder per #forw directive");
867 folder
= pluspath (cp
);
871 /* else, use the current folder */
873 folder
= add (getfolder (1), NULL
);
875 if (!(mp
= folder_read (folder
, 0)))
876 adios (NULL
, "unable to read folder %s", folder
);
877 for (ap
= arguments
; *ap
; ap
++) {
879 if (*cp
!= '+' && *cp
!= '@')
880 if (!m_convert (mp
, cp
))
887 * If there is more than one message to include, make this
888 * a content of type "multipart/digest" and insert each message
889 * as a subpart. If there is only one message, then make this
890 * a content of type "message/rfc822".
892 if (mp
->numsel
> 1) {
893 /* we are forwarding multiple messages */
894 if (get_ctinfo ("multipart/digest", ct
, 0) == NOTOK
)
896 ct
->c_type
= CT_MULTIPART
;
897 ct
->c_subtype
= MULTI_DIGEST
;
899 if ((m
= (struct multipart
*) mh_xcalloc (1, sizeof(*m
))) == NULL
)
900 adios (NULL
, "out of memory");
901 ct
->c_ctparams
= (void *) m
;
904 for (msgnum
= mp
->lowsel
; msgnum
<= mp
->hghsel
; msgnum
++) {
905 if (is_selected(mp
, msgnum
)) {
910 if ((p
= (CT
) mh_xcalloc (1, sizeof(*p
))) == NULL
)
911 adios (NULL
, "out of memory");
912 init_decoded_content (p
, infilename
);
914 if (get_ctinfo ("message/rfc822", p
, 0) == NOTOK
)
916 p
->c_type
= CT_MESSAGE
;
917 p
->c_subtype
= MESSAGE_RFC822
;
919 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
920 pe
->ce_file
= add (buffer
, NULL
);
921 if (listsw
&& stat (pe
->ce_file
, &st
) != NOTOK
)
922 p
->c_end
= (long) st
.st_size
;
924 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
925 adios (NULL
, "out of memory");
932 /* we are forwarding one message */
933 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
935 ct
->c_type
= CT_MESSAGE
;
936 ct
->c_subtype
= MESSAGE_RFC822
;
939 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
940 ce
->ce_file
= add (buffer
, NULL
);
941 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
942 ct
->c_end
= (long) st
.st_size
;
945 folder_free (mp
); /* free folder/message structure */
952 if (!strcasecmp (ci
->ci_type
, "end")) {
959 * #begin [ alternative | parallel ]
961 if (!strcasecmp (ci
->ci_type
, "begin")) {
964 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
965 } else if (!strcasecmp (ci
->ci_magic
, "alternative")) {
966 vrsn
= MULTI_ALTERNATE
;
967 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
968 } else if (!strcasecmp (ci
->ci_magic
, "parallel")) {
969 vrsn
= MULTI_PARALLEL
;
970 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
971 } else if (uprf (ci
->ci_magic
, "digest")) {
974 vrsn
= MULTI_UNKNOWN
;
979 snprintf (buffer
, sizeof(buffer
), "multipart/%s", cp
);
980 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
982 ct
->c_type
= CT_MULTIPART
;
983 ct
->c_subtype
= vrsn
;
985 if ((m
= (struct multipart
*) mh_xcalloc (1, sizeof(*m
))) == NULL
)
986 adios (NULL
, "out of memory");
987 ct
->c_ctparams
= (void *) m
;
990 while (fgetstr (buffer
, sizeof(buffer
) - 1, in
)) {
994 if (user_content (in
, buffer
, &p
, infilename
) == DONE
) {
996 adios (NULL
, "empty \"#begin ... #end\" sequence");
1002 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
1003 adios (NULL
, "out of memory");
1005 pp
= &part
->mp_next
;
1008 admonish (NULL
, "premature end-of-file, missing #end");
1015 adios (NULL
, "unknown directive \"#%s\"", ci
->ci_type
);
1016 return NOTOK
; /* NOT REACHED */
1021 set_id (CT ct
, int top
)
1023 char contentid
[BUFSIZ
];
1025 static time_t clock
= 0;
1026 static char *msgfmt
;
1030 snprintf (contentid
, sizeof(contentid
), "%s\n", message_id (clock
, 1));
1032 msgfmt
= getcpy(contentid
);
1034 snprintf (contentid
, sizeof(contentid
), msgfmt
, top
? 0 : ++partno
);
1035 ct
->c_id
= getcpy (contentid
);
1040 * Fill out, or expand the various contents in the composition
1041 * draft. Read-in any necessary files. Parse and execute any
1042 * commands specified by profile composition strings.
1046 compose_content (CT ct
, int verbose
)
1048 CE ce
= &ct
->c_cefile
;
1050 switch (ct
->c_type
) {
1055 char partnam
[BUFSIZ
];
1056 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1060 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1061 pp
= partnam
+ strlen (partnam
);
1066 /* first, we call compose_content on all the subparts */
1067 for (part
= m
->mp_parts
, partnum
= 1; part
; part
= part
->mp_next
, partnum
++) {
1068 CT p
= part
->mp_part
;
1070 sprintf (pp
, "%d", partnum
);
1071 p
->c_partno
= add (partnam
, NULL
);
1072 if (compose_content (p
, verbose
) == NOTOK
)
1077 * If the -rfc934mode switch is given, then check all
1078 * the subparts of a multipart/digest. If they are all
1079 * message/rfc822, then mark this content and all
1080 * subparts with the rfc934 compatibility mode flag.
1082 if (rfc934sw
&& ct
->c_subtype
== MULTI_DIGEST
) {
1085 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1086 CT p
= part
->mp_part
;
1088 if (p
->c_subtype
!= MESSAGE_RFC822
) {
1093 ct
->c_rfc934
= is934
;
1094 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1095 CT p
= part
->mp_part
;
1097 if ((p
->c_rfc934
= is934
))
1103 ct
->c_end
= (partnum
= strlen (prefix
) + 2) + 2;
1107 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
)
1108 ct
->c_end
+= part
->mp_part
->c_end
+ partnum
;
1114 /* Nothing to do for type message */
1118 * Discrete types (text/application/audio/image/video)
1123 int i
, xstdout
, len
, buflen
;
1125 char *vec
[4], buffer
[BUFSIZ
];
1127 CI ci
= &ct
->c_ctinfo
;
1130 if (!(cp
= ci
->ci_magic
))
1131 adios (NULL
, "internal error(5)");
1133 if ((tfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1134 adios("mhbuildsbr", "unable to create temporary file in %s",
1137 ce
->ce_file
= add (tfile
, NULL
);
1142 /* Get buffer ready to go */
1145 buflen
= sizeof(buffer
);
1148 * Parse composition string into buffer
1150 for ( ; *cp
; cp
++) {
1155 /* insert parameters from directive */
1159 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1160 snprintf (bp
, buflen
, "%s%s=\"%s\"", s
,
1161 pm
->pm_name
, get_param_value(pm
, '?'));
1171 /* %f, and stdout is not-redirected */
1177 * insert temporary filename where
1178 * content should be written
1180 snprintf (bp
, buflen
, "%s", ce
->ce_file
);
1184 /* insert content subtype */
1185 strncpy (bp
, ci
->ci_subtype
, buflen
);
1189 /* insert character % */
1210 printf ("composing content %s/%s from command\n\t%s\n",
1211 ci
->ci_type
, ci
->ci_subtype
, buffer
);
1213 fflush (stdout
); /* not sure if need for -noverbose */
1220 if ((out
= fopen (ce
->ce_file
, "w")) == NULL
)
1221 adios (ce
->ce_file
, "unable to open for writing");
1223 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
> 5; i
++)
1227 adios ("fork", "unable to fork");
1232 dup2 (fileno (out
), 1);
1233 close (fileno (out
));
1234 execvp ("/bin/sh", vec
);
1235 fprintf (stderr
, "unable to exec ");
1242 if (pidXwait(child_id
, NULL
))
1248 /* Check size of file */
1249 if (listsw
&& ct
->c_end
== 0L) {
1252 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1253 ct
->c_end
= (long) st
.st_size
;
1265 * 1) choose a transfer encoding.
1266 * 2) check for clashes with multipart boundary string.
1267 * 3) for text content, figure out which character set is being used.
1269 * If there is a clash with one of the contents and the multipart boundary,
1270 * this function will exit with NOTOK. This will cause the scanning process
1271 * to be repeated with a different multipart boundary. It is possible
1272 * (although highly unlikely) that this scan will be repeated multiple times.
1276 scan_content (CT ct
, size_t maxunencoded
)
1279 int check8bit
= 0, contains8bit
= 0; /* check if contains 8bit data */
1280 int checknul
= 0, containsnul
= 0; /* check if contains NULs */
1281 int checklinelen
= 0, linelen
= 0; /* check for long lines */
1282 int checkllinelen
= 0; /* check for extra-long lines */
1283 int checkboundary
= 0, boundaryclash
= 0; /* check if clashes with multipart boundary */
1284 int checklinespace
= 0, linespace
= 0; /* check if any line ends with space */
1289 struct text
*t
= NULL
;
1291 CE ce
= &ct
->c_cefile
;
1294 * handle multipart by scanning all subparts
1295 * and then checking their encoding.
1297 if (ct
->c_type
== CT_MULTIPART
) {
1298 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1301 /* initially mark the domain of enclosing multipart as 7bit */
1302 ct
->c_encoding
= CE_7BIT
;
1304 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1305 CT p
= part
->mp_part
;
1307 if (scan_content (p
, maxunencoded
) == NOTOK
) /* choose encoding for subpart */
1310 /* if necessary, enlarge encoding for enclosing multipart */
1311 if (p
->c_encoding
== CE_BINARY
)
1312 ct
->c_encoding
= CE_BINARY
;
1313 if (p
->c_encoding
== CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
)
1314 ct
->c_encoding
= CE_8BIT
;
1321 * Decide what to check while scanning this content. Note that
1322 * for text content we always check for 8bit characters if the
1323 * charset is unspecified, because that controls whether or not the
1324 * character set is us-ascii or retrieved from the locale.
1327 if (ct
->c_type
== CT_TEXT
) {
1328 t
= (struct text
*) ct
->c_ctparams
;
1329 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
1335 switch (ct
->c_reqencoding
) {
1346 /* Use the default rules based on content-type */
1347 switch (ct
->c_type
) {
1351 if (ct
->c_subtype
== TEXT_PLAIN
) {
1358 case CT_APPLICATION
:
1370 /* don't check anything for message/external */
1371 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1384 * Don't check anything for these types,
1385 * since we are forcing use of base64, unless
1386 * the content-type was specified by a mhbuild directive.
1397 * Scan the unencoded content
1399 if (check8bit
|| checklinelen
|| checklinespace
|| checkboundary
||
1400 checkllinelen
|| checknul
) {
1401 if ((in
= fopen (ce
->ce_file
, "r")) == NULL
)
1402 adios (ce
->ce_file
, "unable to open for reading");
1403 prefix_len
= strlen (prefix
);
1405 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1407 * Check for 8bit and NUL data.
1409 for (cp
= bufp
; (check8bit
|| checknul
) &&
1410 cp
< bufp
+ gotlen
; cp
++) {
1411 if (!isascii ((unsigned char) *cp
)) {
1413 check8bit
= 0; /* no need to keep checking */
1417 checknul
= 0; /* no need to keep checking */
1422 * Check line length.
1424 if (checklinelen
&& ((size_t)gotlen
> maxunencoded
+ 1)) {
1426 checklinelen
= 0; /* no need to keep checking */
1430 * RFC 5322 specifies that a message cannot contain a line
1431 * greater than 998 characters (excluding the CRLF). If we
1432 * get one of those lines and linelen is NOT set, then abort.
1435 if (checkllinelen
&& !linelen
&&
1436 (gotlen
> MAXLONGLINE
+ 1)) {
1437 adios(NULL
, "Line in content exceeds maximum line limit (%d)",
1442 * Check if line ends with a space.
1444 if (checklinespace
&& (cp
= bufp
+ gotlen
- 2) > bufp
&&
1445 isspace ((unsigned char) *cp
)) {
1447 checklinespace
= 0; /* no need to keep checking */
1451 * Check if content contains a line that clashes
1452 * with our standard boundary for multipart messages.
1454 if (checkboundary
&& bufp
[0] == '-' && bufp
[1] == '-') {
1455 for (cp
= bufp
+ gotlen
- 1; cp
>= bufp
; cp
--)
1456 if (!isspace ((unsigned char) *cp
))
1459 if (!strncmp(bufp
+ 2, prefix
, prefix_len
) &&
1460 isdigit((unsigned char) bufp
[2 + prefix_len
])) {
1462 checkboundary
= 0; /* no need to keep checking */
1471 * If the content is text and didn't specify a character set,
1472 * we need to figure out which one was used.
1475 if (ct
->c_type
== CT_TEXT
) {
1476 t
= (struct text
*) ct
->c_ctparams
;
1477 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
1478 CI ci
= &ct
->c_ctinfo
;
1479 char *eightbitcharset
= write_charset_8bit();
1481 if (contains8bit
&& strcasecmp(eightbitcharset
, "US-ASCII") == 0) {
1482 adios(NULL
, "Text content contains 8 bit characters, but "
1483 "character set is US-ASCII");
1486 add_param(&ci
->ci_first_pm
, &ci
->ci_last_pm
, "charset",
1487 contains8bit
? eightbitcharset
: "us-ascii", 0);
1489 t
->tx_charset
= CHARSET_SPECIFIED
;
1494 * Decide which transfer encoding to use.
1497 if (ct
->c_reqencoding
!= CE_UNKNOWN
)
1498 ct
->c_encoding
= ct
->c_reqencoding
;
1500 int wants_q_p
= (containsnul
|| linelen
|| linespace
|| checksw
);
1502 switch (ct
->c_type
) {
1505 ct
->c_encoding
= CE_QUOTED
;
1506 else if (contains8bit
)
1507 ct
->c_encoding
= CE_8BIT
;
1509 ct
->c_encoding
= CE_7BIT
;
1513 case CT_APPLICATION
:
1514 /* For application type, use base64, except when postscript */
1515 if (wants_q_p
|| contains8bit
) {
1516 if (ct
->c_subtype
== APPLICATION_POSTSCRIPT
)
1517 ct
->c_encoding
= CE_QUOTED
; /* historical */
1519 ct
->c_encoding
= CE_BASE64
;
1521 ct
->c_encoding
= CE_7BIT
;
1526 ct
->c_encoding
= contains8bit
? CE_8BIT
: CE_7BIT
;
1532 /* For audio, image, and video contents, just use base64 */
1533 ct
->c_encoding
= CE_BASE64
;
1538 return (boundaryclash
? NOTOK
: OK
);
1543 * Scan the content structures, and build header
1544 * fields that will need to be output into the
1549 build_headers (CT ct
, int header_encoding
)
1551 int cc
, mailbody
, extbody
, len
;
1552 char *np
, *vp
, buffer
[BUFSIZ
];
1553 CI ci
= &ct
->c_ctinfo
;
1556 * If message is type multipart, then add the multipart
1557 * boundary to the list of attribute/value pairs.
1559 if (ct
->c_type
== CT_MULTIPART
) {
1560 static int level
= 0; /* store nesting level */
1562 snprintf (buffer
, sizeof(buffer
), "%s%d", prefix
, level
++);
1563 add_param(&ci
->ci_first_pm
, &ci
->ci_last_pm
, "boundary", buffer
, 0);
1567 * Skip the output of Content-Type, parameters, content
1568 * description and disposition, and Content-ID if the
1569 * content is of type "message" and the rfc934 compatibility
1570 * flag is set (which means we are inside multipart/digest
1571 * and the switch -rfc934mode was given).
1573 if (ct
->c_type
== CT_MESSAGE
&& ct
->c_rfc934
)
1577 * output the content type and subtype
1579 np
= add (TYPE_FIELD
, NULL
);
1580 vp
= concat (" ", ci
->ci_type
, "/", ci
->ci_subtype
, NULL
);
1582 /* keep track of length of line */
1583 len
= strlen (TYPE_FIELD
) + strlen (ci
->ci_type
)
1584 + strlen (ci
->ci_subtype
) + 3;
1586 extbody
= ct
->c_type
== CT_MESSAGE
&& ct
->c_subtype
== MESSAGE_EXTERNAL
;
1587 mailbody
= extbody
&& ((struct exbody
*) ct
->c_ctparams
)->eb_body
;
1590 * Append the attribute/value pairs to
1591 * the end of the Content-Type line.
1594 if (ci
->ci_first_pm
) {
1595 char *s
= output_params(len
, ci
->ci_first_pm
, &len
, mailbody
);
1598 adios(NULL
, "Internal error: failed outputting Content-Type "
1606 * Append any RFC-822 comment to the end of
1607 * the Content-Type line.
1609 if (ci
->ci_comment
) {
1610 snprintf (buffer
, sizeof(buffer
), "(%s)", ci
->ci_comment
);
1611 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
1612 vp
= add ("\n\t", vp
);
1618 vp
= add (buffer
, vp
);
1621 vp
= add ("\n", vp
);
1622 add_header (ct
, np
, vp
);
1625 * output the Content-ID, unless disabled by -nocontentid
1627 if (contentidsw
&& ct
->c_id
) {
1628 np
= add (ID_FIELD
, NULL
);
1629 vp
= concat (" ", ct
->c_id
, NULL
);
1630 add_header (ct
, np
, vp
);
1633 * output the Content-Description
1636 np
= add (DESCR_FIELD
, NULL
);
1637 vp
= concat (" ", ct
->c_descr
, NULL
);
1638 if (encode_rfc2047(DESCR_FIELD
, &vp
, header_encoding
, NULL
))
1639 adios(NULL
, "Unable to encode %s header", DESCR_FIELD
);
1640 add_header (ct
, np
, vp
);
1644 * output the Content-Disposition. If it's NULL but c_dispo_type is
1645 * set, then we need to build it.
1648 np
= add (DISPO_FIELD
, NULL
);
1649 vp
= concat (" ", ct
->c_dispo
, NULL
);
1650 add_header (ct
, np
, vp
);
1651 } else if (ct
->c_dispo_type
) {
1652 vp
= concat (" ", ct
->c_dispo_type
, NULL
);
1653 len
= strlen(DISPO_FIELD
) + strlen(vp
) + 1;
1654 np
= output_params(len
, ct
->c_dispo_first
, NULL
, 0);
1659 add_header (ct
, getcpy(DISPO_FIELD
), vp
);
1664 * If this is the internal content structure for a
1665 * "message/external", then we are done with the
1666 * headers (since it has no body).
1672 * output the Content-MD5
1675 np
= add (MD5_FIELD
, NULL
);
1676 vp
= calculate_digest (ct
, (ct
->c_encoding
== CE_QUOTED
) ? 1 : 0);
1677 add_header (ct
, np
, vp
);
1681 * output the Content-Transfer-Encoding
1683 switch (ct
->c_encoding
) {
1685 /* Nothing to output */
1689 np
= add (ENCODING_FIELD
, NULL
);
1690 vp
= concat (" ", "8bit", "\n", NULL
);
1691 add_header (ct
, np
, vp
);
1695 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1696 adios (NULL
, "internal error, invalid encoding");
1698 np
= add (ENCODING_FIELD
, NULL
);
1699 vp
= concat (" ", "quoted-printable", "\n", NULL
);
1700 add_header (ct
, np
, vp
);
1704 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1705 adios (NULL
, "internal error, invalid encoding");
1707 np
= add (ENCODING_FIELD
, NULL
);
1708 vp
= concat (" ", "base64", "\n", NULL
);
1709 add_header (ct
, np
, vp
);
1713 if (ct
->c_type
== CT_MESSAGE
)
1714 adios (NULL
, "internal error, invalid encoding");
1716 np
= add (ENCODING_FIELD
, NULL
);
1717 vp
= concat (" ", "binary", "\n", NULL
);
1718 add_header (ct
, np
, vp
);
1722 adios (NULL
, "unknown transfer encoding in content");
1727 * Additional content specific header processing
1729 switch (ct
->c_type
) {
1732 struct multipart
*m
;
1735 m
= (struct multipart
*) ct
->c_ctparams
;
1736 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1740 build_headers (p
, header_encoding
);
1746 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1749 e
= (struct exbody
*) ct
->c_ctparams
;
1750 build_headers (e
->eb_content
, header_encoding
);
1763 static char nib2b64
[0x40+1] =
1764 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1767 calculate_digest (CT ct
, int asciiP
)
1772 unsigned char digest
[16];
1773 unsigned char outbuf
[25];
1775 CE ce
= &ct
->c_cefile
;
1776 char *infilename
= ce
->ce_file
? ce
->ce_file
: ct
->c_file
;
1780 if ((in
= fopen (infilename
, "r")) == NULL
)
1781 adios (infilename
, "unable to open for reading");
1783 /* Initialize md5 context */
1784 MD5Init (&mdContext
);
1786 /* calculate md5 message digest */
1791 while ((gotlen
= getline(&bufp
, &buflen
, in
)) != -1) {
1794 cp
= bufp
+ gotlen
- 1;
1795 if ((c
= *cp
) == '\n')
1798 MD5Update (&mdContext
, (unsigned char *) bufp
,
1799 (unsigned int) gotlen
);
1802 MD5Update (&mdContext
, (unsigned char *) "\r\n", 2);
1805 char buffer
[BUFSIZ
];
1806 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), in
)) > 0)
1807 MD5Update (&mdContext
, (unsigned char *) buffer
, (unsigned int) cc
);
1810 /* md5 finalization. Write digest and zero md5 context */
1811 MD5Final (digest
, &mdContext
);
1816 /* print debugging info */
1820 fprintf (stderr
, "MD5 digest=");
1821 for (ep
= (dp
= digest
) + sizeof(digest
) / sizeof(digest
[0]);
1823 fprintf (stderr
, "%02x", *dp
& 0xff);
1824 fprintf (stderr
, "\n");
1827 /* encode the digest using base64 */
1828 for (dp
= digest
, op
= (char *) outbuf
,
1829 cc
= sizeof(digest
) / sizeof(digest
[0]);
1830 cc
> 0; cc
-= 3, op
+= 4) {
1834 bits
= (*dp
++ & 0xff) << 16;
1836 bits
|= (*dp
++ & 0xff) << 8;
1838 bits
|= *dp
++ & 0xff;
1841 for (bp
= op
+ 4; bp
> op
; bits
>>= 6)
1842 *--bp
= nib2b64
[bits
& 0x3f];
1850 /* null terminate string */
1853 /* now make copy and return string */
1854 vp
= concat (" ", outbuf
, "\n", NULL
);
1859 * Set things up for the content structure for file "filename" that
1864 setup_attach_content(CT ct
, char *filename
)
1866 char *type
, *simplename
= r1bindex(filename
, '/');
1867 struct str2init
*s2i
;
1871 if (! (type
= mime_type(filename
))) {
1872 adios(NULL
, "Unable to determine MIME type of \"%s\"", filename
);
1876 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1877 * since we're just feeding it a MIME type we have to add those ourself.
1878 * Map that to a valid content-type label and call any initialization
1882 if (get_ctinfo(type
, ct
, 0) == NOTOK
)
1887 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
1888 if (strcasecmp(ct
->c_ctinfo
.ci_type
, s2i
->si_key
) == 0)
1890 if (!s2i
->si_key
&& !uprf(ct
->c_ctinfo
.ci_type
, "X-"))
1894 * Make sure the type isn't incompatible with what we can handle
1897 switch (ct
->c_type
= s2i
->si_val
) {
1899 adios (NULL
, "multipart types must be specified by mhbuild directives");
1903 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "partial") == 0)
1904 adios(NULL
, "Sorry, %s/%s isn't supported", ct
->c_ctinfo
.ci_type
,
1905 ct
->c_ctinfo
.ci_subtype
);
1906 if (strcasecmp(ct
->c_ctinfo
.ci_subtype
, "external-body") == 0)
1907 adios(NULL
, "external-body messages must be specified "
1908 "by mhbuild directives");
1913 * This sets the subtype, if it's significant
1915 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
1916 (*ct
->c_ctinitfnx
)(ct
);
1921 * Feed in a few attributes; specifically, the name attribute, the
1922 * content-description, and the content-disposition.
1925 for (pm
= ct
->c_ctinfo
.ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1926 if (strcasecmp(pm
->pm_name
, "name") == 0) {
1929 pm
->pm_value
= getcpy(simplename
);
1935 add_param(&ct
->c_ctinfo
.ci_first_pm
, &ct
->c_ctinfo
.ci_last_pm
,
1936 "name", simplename
, 0);
1938 ct
->c_descr
= getcpy(simplename
);
1939 ct
->c_descr
= add("\n", ct
->c_descr
);
1940 ct
->c_cefile
.ce_file
= getcpy(filename
);
1943 * Look for mhbuild-disposition-<type>/<subtype> entry
1944 * that specifies Content-Disposition type. Only
1945 * 'attachment' and 'inline' are allowed. Default to
1949 cp
= context_find_by_type ("disposition", ct
->c_ctinfo
.ci_type
,
1950 ct
->c_ctinfo
.ci_subtype
);
1952 if (strcasecmp (cp
, "attachment") && strcasecmp (cp
, "inline")) {
1953 admonish (NULL
, "configuration problem: %s-disposition-%s%s%s "
1954 "specifies '%s' but only 'attachment' and 'inline' are "
1955 "allowed", invo_name
,
1956 ct
->c_ctinfo
.ci_type
,
1957 ct
->c_ctinfo
.ci_subtype
? "/" : "",
1958 ct
->c_ctinfo
.ci_subtype
? ct
->c_ctinfo
.ci_subtype
: "",
1964 ct
->c_dispo_type
= getcpy(cp
);
1966 ct
->c_dispo_type
= getcpy("attachment");
1969 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename", simplename
, 0);