]>
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>
27 #include <h/mhparse.h>
30 #ifdef HAVE_SYS_TIME_H
31 # include <sys/time.h>
41 extern int contentidsw
;
44 extern int rcachesw
; /* mhcachesbr.c */
45 extern int wcachesw
; /* mhcachesbr.c */
48 * Directory to place tmp files. This must
49 * be set before these routines are called.
55 static char prefix
[] = "----- =_aaaaaaaaaa";
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 CT
build_mime (char *, int);
76 static int init_decoded_content (CT
);
77 static char *fgetstr (char *, int, FILE *);
78 static int user_content (FILE *, char *, char *, CT
*);
79 static void set_id (CT
, int);
80 static int compose_content (CT
);
81 static int scan_content (CT
);
82 static int build_headers (CT
);
83 static char *calculate_digest (CT
, int);
86 static unsigned char directives_stack
[32];
87 static unsigned int directives_index
;
89 static int do_direct(void)
91 return directives_stack
[directives_index
];
94 static void directive_onoff(int onoff
)
96 if (directives_index
>= sizeof(directives_stack
) - 1) {
97 fprintf(stderr
, "mhbuild: #on/off overflow, continuing\n");
100 directives_stack
[++directives_index
] = onoff
;
103 static void directive_init(int onoff
)
105 directives_index
= 0;
106 directives_stack
[0] = onoff
;
109 static void directive_pop(void)
111 if (directives_index
> 0)
114 fprintf(stderr
, "mhbuild: #pop underflow, continuing\n");
118 * Main routine for translating composition file
119 * into valid MIME message. It translates the draft
120 * into a content structure (actually a tree of content
121 * structures). This message then can be manipulated
122 * in various ways, including being output via
127 build_mime (char *infile
, int directives
)
130 char buf
[BUFSIZ
], name
[NAMESZ
];
136 m_getfld_state_t gstate
= 0;
138 directive_init(directives
);
140 umask (~m_gmprot ());
142 /* open the composition draft */
143 if ((in
= fopen (infile
, "r")) == NULL
)
144 adios (infile
, "unable to open for reading");
147 * Allocate space for primary (outside) content
149 if ((ct
= (CT
) calloc (1, sizeof(*ct
))) == NULL
)
150 adios (NULL
, "out of memory");
153 * Allocate structure for handling decoded content
154 * for this part. We don't really need this, but
155 * allocate it to remain consistent.
157 init_decoded_content (ct
);
160 * Parse some of the header fields in the composition
161 * draft into the linked list of header fields for
162 * the new MIME message.
164 m_getfld_track_filepos (&gstate
, in
);
165 for (compnum
= 1;;) {
166 int bufsz
= sizeof buf
;
167 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
172 /* abort if draft has Mime-Version header field */
173 if (!mh_strcasecmp (name
, VRSN_FIELD
))
174 adios (NULL
, "draft shouldn't contain %s: field", VRSN_FIELD
);
176 /* abort if draft has Content-Transfer-Encoding header field */
177 if (!mh_strcasecmp (name
, ENCODING_FIELD
))
178 adios (NULL
, "draft shouldn't contain %s: field", ENCODING_FIELD
);
180 /* ignore any Content-Type fields in the header */
181 if (!mh_strcasecmp (name
, TYPE_FIELD
)) {
182 while (state
== FLDPLUS
) {
184 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
189 /* get copies of the buffers */
190 np
= add (name
, NULL
);
191 vp
= add (buf
, NULL
);
193 /* if necessary, get rest of field */
194 while (state
== FLDPLUS
) {
196 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
197 vp
= add (buf
, vp
); /* add to previous value */
200 /* Now add the header data to the list */
201 add_header (ct
, np
, vp
);
204 /* if this wasn't the last header field, then continue */
208 adios (NULL
, "draft has empty body -- no directives!");
212 fseek (in
, (long) (-strlen (buf
)), SEEK_CUR
);
217 adios (NULL
, "message format error in component #%d", compnum
);
220 adios (NULL
, "getfld() returned %d", state
);
224 m_getfld_state_destroy (&gstate
);
227 * Now add the MIME-Version header field
228 * to the list of header fields.
230 np
= add (VRSN_FIELD
, NULL
);
231 vp
= concat (" ", VRSN_VALUE
, "\n", NULL
);
232 add_header (ct
, np
, vp
);
235 * We initally assume we will find multiple contents in the
236 * draft. So create a multipart/mixed content to hold everything.
237 * We can remove this later, if it is not needed.
239 if (get_ctinfo ("multipart/mixed", ct
, 0) == NOTOK
)
241 ct
->c_type
= CT_MULTIPART
;
242 ct
->c_subtype
= MULTI_MIXED
;
243 ct
->c_file
= add (infile
, NULL
);
245 if ((m
= (struct multipart
*) calloc (1, sizeof(*m
))) == NULL
)
246 adios (NULL
, "out of memory");
247 ct
->c_ctparams
= (void *) m
;
251 * read and parse the composition file
252 * and the directives it contains.
254 while (fgetstr (buf
, sizeof(buf
) - 1, in
)) {
258 if (user_content (in
, infile
, buf
, &p
) == DONE
) {
259 admonish (NULL
, "ignoring spurious #end");
265 if ((part
= (struct part
*) calloc (1, sizeof(*part
))) == NULL
)
266 adios (NULL
, "out of memory");
273 * close the composition draft since
274 * it's not needed any longer.
278 /* check if any contents were found */
280 adios (NULL
, "no content directives found");
283 * If only one content was found, then remove and
284 * free the outer multipart content.
286 if (!m
->mp_parts
->mp_next
) {
289 p
= m
->mp_parts
->mp_part
;
290 m
->mp_parts
->mp_part
= NULL
;
292 /* move header fields */
293 p
->c_first_hf
= ct
->c_first_hf
;
294 p
->c_last_hf
= ct
->c_last_hf
;
295 ct
->c_first_hf
= NULL
;
296 ct
->c_last_hf
= NULL
;
305 * Fill out, or expand directives. Parse and execute
306 * commands specified by profile composition strings.
308 compose_content (ct
);
310 if ((cp
= strchr(prefix
, 'a')) == NULL
)
311 adios (NULL
, "internal error(4)");
314 * Scan the contents. Choose a transfer encoding, and
315 * check if prefix for multipart boundary clashes with
316 * any of the contents.
318 while (scan_content (ct
) == NOTOK
) {
323 adios (NULL
, "giving up trying to find a unique delimiter string");
329 /* Build the rest of the header field structures */
337 * Set up structures for placing unencoded
338 * content when building parts.
342 init_decoded_content (CT ct
)
346 if ((ce
= (CE
) calloc (1, sizeof(*ce
))) == NULL
)
347 adios (NULL
, "out of memory");
350 ct
->c_ceopenfnx
= open7Bit
; /* since unencoded */
351 ct
->c_ceclosefnx
= close_encoding
;
352 ct
->c_cesizefnx
= NULL
; /* since unencoded */
359 fgetstr (char *s
, int n
, FILE *stream
)
365 for (ep
= (cp
= s
) + o_n
; cp
< ep
; ) {
368 if (!fgets (cp
, n
, stream
))
369 return (cp
!= s
? s
: NULL
);
371 if (cp
== s
&& *cp
!= '#')
374 cp
+= (i
= strlen (cp
)) - 1;
375 if (i
<= 1 || *cp
-- != '\n' || *cp
!= '\\')
381 if (strcmp(s
, "#on\n") == 0) {
383 } else if (strcmp(s
, "#off\n") == 0) {
385 } else if (strcmp(s
, "#pop\n") == 0) {
397 * Parse the composition draft for text and directives.
398 * Do initial setup of Content structure.
402 user_content (FILE *in
, char *file
, char *buf
, CT
*ctp
)
410 struct str2init
*s2i
;
415 if (buf
[0] == '\n' || (do_direct() && strcmp (buf
, "#\n") == 0)) {
420 /* allocate basic Content structure */
421 if ((ct
= (CT
) calloc (1, sizeof(*ct
))) == NULL
)
422 adios (NULL
, "out of memory");
425 /* allocate basic structure for handling decoded content */
426 init_decoded_content (ct
);
433 * Handle inline text. Check if line
434 * is one of the following forms:
436 * 1) doesn't begin with '#' (implicit directive)
437 * 2) begins with "##" (implicit directive)
438 * 3) begins with "#<"
440 if (!do_direct() || buf
[0] != '#' || buf
[1] == '#' || buf
[1] == '<') {
444 char content
[BUFSIZ
];
448 cp
= m_mktemp2(NULL
, invo_name
, NULL
, &out
);
449 if (cp
== NULL
) adios("mhbuildsbr", "unable to create temporary file");
451 /* use a temp file to collect the plain text lines */
452 ce
->ce_file
= add (cp
, NULL
);
455 if (do_direct() && (buf
[0] == '#' && buf
[1] == '<')) {
456 strncpy (content
, buf
+ 2, sizeof(content
));
463 /* the directive is implicit */
464 strncpy (content
, "text/plain", sizeof(content
));
466 strncpy (buffer
, (!do_direct() || buf
[0] != '#') ? buf
: buf
+ 1, sizeof(buffer
));
470 if (headers
>= 0 && do_direct() && uprf (buffer
, DESCR_FIELD
)
471 && buffer
[i
= strlen (DESCR_FIELD
)] == ':') {
475 ct
->c_descr
= add (buffer
+ i
+ 1, ct
->c_descr
);
476 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
477 adios (NULL
, "end-of-file after %s: field in plaintext", DESCR_FIELD
);
485 adios (NULL
, "#-directive after %s: field in plaintext", DESCR_FIELD
);
493 if (headers
>= 0 && do_direct() && uprf (buffer
, DISPO_FIELD
)
494 && buffer
[i
= strlen (DISPO_FIELD
)] == ':') {
498 ct
->c_dispo
= add (buffer
+ i
+ 1, ct
->c_dispo
);
499 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
500 adios (NULL
, "end-of-file after %s: field in plaintext", DISPO_FIELD
);
508 adios (NULL
, "#-directive after %s: field in plaintext", DISPO_FIELD
);
516 if (headers
!= 1 || buffer
[0] != '\n')
522 if ((cp
= fgetstr (buffer
, sizeof(buffer
) - 1, in
)) == NULL
)
524 if (do_direct() && buffer
[0] == '#') {
527 if (buffer
[1] != '#')
529 for (cp
= (bp
= buffer
) + 1; *cp
; cp
++)
536 ct
->c_end
= ftell (out
);
539 /* parse content type */
540 if (get_ctinfo (content
, ct
, inlineD
) == NOTOK
)
543 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
544 if (!mh_strcasecmp (ci
->ci_type
, s2i
->si_key
))
546 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
550 * check type specified (possibly implicitly)
552 switch (ct
->c_type
= s2i
->si_val
) {
554 if (!mh_strcasecmp (ci
->ci_subtype
, "rfc822")) {
555 ct
->c_encoding
= CE_7BIT
;
560 adios (NULL
, "it doesn't make sense to define an in-line %s content",
561 ct
->c_type
== CT_MESSAGE
? "message" : "multipart");
566 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
567 (*ct
->c_ctinitfnx
) (ct
);
572 fseek (in
, pos
, SEEK_SET
);
577 * If we've reached this point, the next line
578 * must be some type of explicit directive.
581 /* check if directive is external-type */
582 extrnal
= (buf
[1] == '@');
584 /* parse directive */
585 if (get_ctinfo (buf
+ (extrnal
? 2 : 1), ct
, 1) == NOTOK
)
588 /* check directive against the list of MIME types */
589 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
590 if (!mh_strcasecmp (ci
->ci_type
, s2i
->si_key
))
594 * Check if the directive specified a valid type.
595 * This will happen if it was one of the following forms:
602 adios (NULL
, "missing subtype in \"#%s\"", ci
->ci_type
);
604 switch (ct
->c_type
= s2i
->si_val
) {
606 adios (NULL
, "use \"#begin ... #end\" instead of \"#%s/%s\"",
607 ci
->ci_type
, ci
->ci_subtype
);
611 if (!mh_strcasecmp (ci
->ci_subtype
, "partial"))
612 adios (NULL
, "sorry, \"#%s/%s\" isn't supported",
613 ci
->ci_type
, ci
->ci_subtype
);
614 if (!mh_strcasecmp (ci
->ci_subtype
, "external-body"))
615 adios (NULL
, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
616 ci
->ci_type
, ci
->ci_subtype
);
619 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
620 ci
->ci_type
, ci
->ci_subtype
);
624 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
625 (*ct
->c_ctinitfnx
) (ct
);
630 * #@type/subtype (external types directive)
637 adios (NULL
, "need external information for \"#@%s/%s\"",
638 ci
->ci_type
, ci
->ci_subtype
);
641 snprintf (buffer
, sizeof(buffer
), "message/external-body; %s", ci
->ci_magic
);
646 * Since we are using the current Content structure to
647 * hold information about the type of the external
648 * reference, we need to create another Content structure
649 * for the message/external-body to wrap it in.
651 if ((ct
= (CT
) calloc (1, sizeof(*ct
))) == NULL
)
652 adios (NULL
, "out of memory");
655 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
657 ct
->c_type
= CT_MESSAGE
;
658 ct
->c_subtype
= MESSAGE_EXTERNAL
;
660 if ((e
= (struct exbody
*) calloc (1, sizeof(*e
))) == NULL
)
661 adios (NULL
, "out of memory");
662 ct
->c_ctparams
= (void *) e
;
668 if (params_external (ct
, 1) == NOTOK
)
674 /* Handle [file] argument */
676 /* check if specifies command to execute */
677 if (*ci
->ci_magic
== '|' || *ci
->ci_magic
== '!') {
678 for (cp
= ci
->ci_magic
+ 1; isspace ((unsigned char) *cp
); cp
++)
681 adios (NULL
, "empty pipe command for #%s directive", ci
->ci_type
);
686 /* record filename of decoded contents */
687 ce
->ce_file
= ci
->ci_magic
;
688 if (access (ce
->ce_file
, R_OK
) == NOTOK
)
689 adios ("reading", "unable to access %s for", ce
->ce_file
);
690 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
691 ct
->c_end
= (long) st
.st_size
;
698 * No [file] argument, so check profile for
699 * method to compose content.
701 snprintf (buffer
, sizeof(buffer
), "%s-compose-%s/%s",
702 invo_name
, ci
->ci_type
, ci
->ci_subtype
);
703 if ((cp
= context_find (buffer
)) == NULL
|| *cp
== '\0') {
704 snprintf (buffer
, sizeof(buffer
), "%s-compose-%s", invo_name
, ci
->ci_type
);
705 if ((cp
= context_find (buffer
)) == NULL
|| *cp
== '\0') {
706 content_error (NULL
, ct
, "don't know how to compose content");
710 ci
->ci_magic
= add (cp
, NULL
);
715 adios (NULL
, "external definition not allowed for \"#%s\"", ci
->ci_type
);
719 * #forw [+folder] [msgs]
721 if (!mh_strcasecmp (ci
->ci_type
, "forw")) {
723 char *folder
, *arguments
[MAXARGS
];
727 ap
= brkstring (ci
->ci_magic
, " ", "\n");
728 copyip (ap
, arguments
, MAXARGS
);
730 arguments
[0] = "cur";
735 /* search the arguments for a folder name */
736 for (ap
= arguments
; *ap
; ap
++) {
738 if (*cp
== '+' || *cp
== '@') {
740 adios (NULL
, "only one folder per #forw directive");
742 folder
= pluspath (cp
);
746 /* else, use the current folder */
748 folder
= add (getfolder (1), NULL
);
750 if (!(mp
= folder_read (folder
)))
751 adios (NULL
, "unable to read folder %s", folder
);
752 for (ap
= arguments
; *ap
; ap
++) {
754 if (*cp
!= '+' && *cp
!= '@')
755 if (!m_convert (mp
, cp
))
762 * If there is more than one message to include, make this
763 * a content of type "multipart/digest" and insert each message
764 * as a subpart. If there is only one message, then make this
765 * a content of type "message/rfc822".
767 if (mp
->numsel
> 1) {
768 /* we are forwarding multiple messages */
769 if (get_ctinfo ("multipart/digest", ct
, 0) == NOTOK
)
771 ct
->c_type
= CT_MULTIPART
;
772 ct
->c_subtype
= MULTI_DIGEST
;
774 if ((m
= (struct multipart
*) calloc (1, sizeof(*m
))) == NULL
)
775 adios (NULL
, "out of memory");
776 ct
->c_ctparams
= (void *) m
;
779 for (msgnum
= mp
->lowsel
; msgnum
<= mp
->hghsel
; msgnum
++) {
780 if (is_selected(mp
, msgnum
)) {
785 if ((p
= (CT
) calloc (1, sizeof(*p
))) == NULL
)
786 adios (NULL
, "out of memory");
787 init_decoded_content (p
);
789 if (get_ctinfo ("message/rfc822", p
, 0) == NOTOK
)
791 p
->c_type
= CT_MESSAGE
;
792 p
->c_subtype
= MESSAGE_RFC822
;
794 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
795 pe
->ce_file
= add (buffer
, NULL
);
796 if (listsw
&& stat (pe
->ce_file
, &st
) != NOTOK
)
797 p
->c_end
= (long) st
.st_size
;
799 if ((part
= (struct part
*) calloc (1, sizeof(*part
))) == NULL
)
800 adios (NULL
, "out of memory");
807 /* we are forwarding one message */
808 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
810 ct
->c_type
= CT_MESSAGE
;
811 ct
->c_subtype
= MESSAGE_RFC822
;
814 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
815 ce
->ce_file
= add (buffer
, NULL
);
816 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
817 ct
->c_end
= (long) st
.st_size
;
820 folder_free (mp
); /* free folder/message structure */
827 if (!mh_strcasecmp (ci
->ci_type
, "end")) {
834 * #begin [ alternative | parallel ]
836 if (!mh_strcasecmp (ci
->ci_type
, "begin")) {
839 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
840 } else if (!mh_strcasecmp (ci
->ci_magic
, "alternative")) {
841 vrsn
= MULTI_ALTERNATE
;
842 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
843 } else if (!mh_strcasecmp (ci
->ci_magic
, "parallel")) {
844 vrsn
= MULTI_PARALLEL
;
845 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
846 } else if (uprf (ci
->ci_magic
, "digest")) {
849 vrsn
= MULTI_UNKNOWN
;
854 snprintf (buffer
, sizeof(buffer
), "multipart/%s", cp
);
855 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
857 ct
->c_type
= CT_MULTIPART
;
858 ct
->c_subtype
= vrsn
;
860 if ((m
= (struct multipart
*) calloc (1, sizeof(*m
))) == NULL
)
861 adios (NULL
, "out of memory");
862 ct
->c_ctparams
= (void *) m
;
865 while (fgetstr (buffer
, sizeof(buffer
) - 1, in
)) {
869 if (user_content (in
, file
, buffer
, &p
) == DONE
) {
871 adios (NULL
, "empty \"#begin ... #end\" sequence");
877 if ((part
= (struct part
*) calloc (1, sizeof(*part
))) == NULL
)
878 adios (NULL
, "out of memory");
883 admonish (NULL
, "premature end-of-file, missing #end");
890 adios (NULL
, "unknown directive \"#%s\"", ci
->ci_type
);
891 return NOTOK
; /* NOT REACHED */
896 set_id (CT ct
, int top
)
898 char contentid
[BUFSIZ
];
900 static time_t clock
= 0;
905 snprintf (contentid
, sizeof(contentid
), "%s\n", message_id (clock
, 1));
907 msgfmt
= getcpy(contentid
);
909 snprintf (contentid
, sizeof(contentid
), msgfmt
, top
? 0 : ++partno
);
910 ct
->c_id
= getcpy (contentid
);
915 * Fill out, or expand the various contents in the composition
916 * draft. Read-in any necessary files. Parse and execute any
917 * commands specified by profile composition strings.
921 compose_content (CT ct
)
923 CE ce
= ct
->c_cefile
;
925 switch (ct
->c_type
) {
930 char partnam
[BUFSIZ
];
931 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
935 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
936 pp
= partnam
+ strlen (partnam
);
941 /* first, we call compose_content on all the subparts */
942 for (part
= m
->mp_parts
, partnum
= 1; part
; part
= part
->mp_next
, partnum
++) {
943 CT p
= part
->mp_part
;
945 sprintf (pp
, "%d", partnum
);
946 p
->c_partno
= add (partnam
, NULL
);
947 if (compose_content (p
) == NOTOK
)
952 * If the -rfc934mode switch is given, then check all
953 * the subparts of a multipart/digest. If they are all
954 * message/rfc822, then mark this content and all
955 * subparts with the rfc934 compatibility mode flag.
957 if (rfc934sw
&& ct
->c_subtype
== MULTI_DIGEST
) {
960 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
961 CT p
= part
->mp_part
;
963 if (p
->c_subtype
!= MESSAGE_RFC822
) {
968 ct
->c_rfc934
= is934
;
969 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
970 CT p
= part
->mp_part
;
972 if ((p
->c_rfc934
= is934
))
978 ct
->c_end
= (partnum
= strlen (prefix
) + 2) + 2;
982 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
)
983 ct
->c_end
+= part
->mp_part
->c_end
+ partnum
;
989 /* Nothing to do for type message */
993 * Discrete types (text/application/audio/image/video)
998 int i
, xstdout
, len
, buflen
;
1000 char *vec
[4], buffer
[BUFSIZ
];
1002 CI ci
= &ct
->c_ctinfo
;
1005 if (!(cp
= ci
->ci_magic
))
1006 adios (NULL
, "internal error(5)");
1008 tfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
);
1009 if (tfile
== NULL
) {
1010 adios("mhbuildsbr", "unable to create temporary file");
1012 ce
->ce_file
= add (tfile
, NULL
);
1017 /* Get buffer ready to go */
1020 buflen
= sizeof(buffer
);
1023 * Parse composition string into buffer
1025 for ( ; *cp
; cp
++) {
1030 /* insert parameters from directive */
1034 for (ap
= ci
->ci_attrs
, ep
= ci
->ci_values
; *ap
; ap
++, ep
++) {
1035 snprintf (bp
, buflen
, "%s%s=\"%s\"", s
, *ap
, *ep
);
1045 /* %f, and stdout is not-redirected */
1051 * insert temporary filename where
1052 * content should be written
1054 snprintf (bp
, buflen
, "%s", ce
->ce_file
);
1058 /* insert content subtype */
1059 strncpy (bp
, ci
->ci_subtype
, buflen
);
1063 /* insert character % */
1084 printf ("composing content %s/%s from command\n\t%s\n",
1085 ci
->ci_type
, ci
->ci_subtype
, buffer
);
1087 fflush (stdout
); /* not sure if need for -noverbose */
1094 if ((out
= fopen (ce
->ce_file
, "w")) == NULL
)
1095 adios (ce
->ce_file
, "unable to open for writing");
1097 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
> 5; i
++)
1101 adios ("fork", "unable to fork");
1106 dup2 (fileno (out
), 1);
1107 close (fileno (out
));
1108 execvp ("/bin/sh", vec
);
1109 fprintf (stderr
, "unable to exec ");
1116 if (pidXwait(child_id
, NULL
))
1122 /* Check size of file */
1123 if (listsw
&& ct
->c_end
== 0L) {
1126 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1127 ct
->c_end
= (long) st
.st_size
;
1139 * 1) choose a transfer encoding.
1140 * 2) check for clashes with multipart boundary string.
1141 * 3) for text content, figure out which character set is being used.
1143 * If there is a clash with one of the contents and the multipart boundary,
1144 * this function will exit with NOTOK. This will cause the scanning process
1145 * to be repeated with a different multipart boundary. It is possible
1146 * (although highly unlikely) that this scan will be repeated multiple times.
1150 scan_content (CT ct
)
1153 int check8bit
= 0, contains8bit
= 0; /* check if contains 8bit data */
1154 int checklinelen
= 0, linelen
= 0; /* check for long lines */
1155 int checkboundary
= 0, boundaryclash
= 0; /* check if clashes with multipart boundary */
1156 int checklinespace
= 0, linespace
= 0; /* check if any line ends with space */
1157 char *cp
= NULL
, buffer
[BUFSIZ
];
1158 struct text
*t
= NULL
;
1160 CE ce
= ct
->c_cefile
;
1163 * handle multipart by scanning all subparts
1164 * and then checking their encoding.
1166 if (ct
->c_type
== CT_MULTIPART
) {
1167 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1170 /* initially mark the domain of enclosing multipart as 7bit */
1171 ct
->c_encoding
= CE_7BIT
;
1173 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1174 CT p
= part
->mp_part
;
1176 if (scan_content (p
) == NOTOK
) /* choose encoding for subpart */
1179 /* if necessary, enlarge encoding for enclosing multipart */
1180 if (p
->c_encoding
== CE_BINARY
)
1181 ct
->c_encoding
= CE_BINARY
;
1182 if (p
->c_encoding
== CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
)
1183 ct
->c_encoding
= CE_8BIT
;
1190 * Decide what to check while scanning this content.
1192 switch (ct
->c_type
) {
1196 if (ct
->c_subtype
== TEXT_PLAIN
) {
1205 case CT_APPLICATION
:
1217 /* don't check anything for message/external */
1218 if (ct
->c_subtype
== MESSAGE_EXTERNAL
)
1228 * Don't check anything for these types,
1229 * since we are forcing use of base64.
1239 * Scan the unencoded content
1241 if (check8bit
|| checklinelen
|| checklinespace
|| checkboundary
) {
1242 if ((in
= fopen (ce
->ce_file
, "r")) == NULL
)
1243 adios (ce
->ce_file
, "unable to open for reading");
1244 len
= strlen (prefix
);
1246 while (fgets (buffer
, sizeof(buffer
) - 1, in
)) {
1248 * Check for 8bit data.
1251 for (cp
= buffer
; *cp
; cp
++) {
1252 if (!isascii ((unsigned char) *cp
)) {
1254 check8bit
= 0; /* no need to keep checking */
1260 * Check line length.
1262 if (checklinelen
&& (strlen (buffer
) > CPERLIN
+ 1)) {
1264 checklinelen
= 0; /* no need to keep checking */
1268 * Check if line ends with a space.
1270 if (checklinespace
&& (cp
= buffer
+ strlen (buffer
) - 2) > buffer
&& isspace ((unsigned char) *cp
)) {
1272 checklinespace
= 0; /* no need to keep checking */
1276 * Check if content contains a line that clashes
1277 * with our standard boundary for multipart messages.
1279 if (checkboundary
&& buffer
[0] == '-' && buffer
[1] == '-') {
1280 for (cp
= buffer
+ strlen (buffer
) - 1; cp
>= buffer
; cp
--)
1281 if (!isspace ((unsigned char) *cp
))
1284 if (!strncmp(buffer
+ 2, prefix
, len
) && isdigit((unsigned char) buffer
[2 + len
])) {
1286 checkboundary
= 0; /* no need to keep checking */
1294 * Decide which transfer encoding to use.
1296 switch (ct
->c_type
) {
1299 * If the text content didn't specify a character
1300 * set, we need to figure out which one was used.
1302 t
= (struct text
*) ct
->c_ctparams
;
1303 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
1304 CI ci
= &ct
->c_ctinfo
;
1307 for (ap
= ci
->ci_attrs
, ep
= ci
->ci_values
; *ap
; ap
++, ep
++)
1311 *ap
= concat ("charset=", write_charset_8bit(), NULL
);
1313 *ap
= add ("charset=us-ascii", NULL
);
1315 t
->tx_charset
= CHARSET_SPECIFIED
;
1317 cp
= strchr(*ap
++, '=');
1323 if (contains8bit
|| linelen
|| linespace
|| checksw
)
1324 ct
->c_encoding
= CE_QUOTED
;
1326 ct
->c_encoding
= CE_7BIT
;
1329 case CT_APPLICATION
:
1330 /* For application type, use base64, except when postscript */
1331 if (contains8bit
|| linelen
|| linespace
|| checksw
)
1332 ct
->c_encoding
= (ct
->c_subtype
== APPLICATION_POSTSCRIPT
)
1333 ? CE_QUOTED
: CE_BASE64
;
1335 ct
->c_encoding
= CE_7BIT
;
1339 ct
->c_encoding
= CE_7BIT
;
1345 /* For audio, image, and video contents, just use base64 */
1346 ct
->c_encoding
= CE_BASE64
;
1350 return (boundaryclash
? NOTOK
: OK
);
1355 * Scan the content structures, and build header
1356 * fields that will need to be output into the
1361 build_headers (CT ct
)
1363 int cc
, mailbody
, len
;
1365 char *np
, *vp
, buffer
[BUFSIZ
];
1366 CI ci
= &ct
->c_ctinfo
;
1369 * If message is type multipart, then add the multipart
1370 * boundary to the list of attribute/value pairs.
1372 if (ct
->c_type
== CT_MULTIPART
) {
1374 static int level
= 0; /* store nesting level */
1378 snprintf (buffer
, sizeof(buffer
), "boundary=%s%d", prefix
, level
++);
1379 cp
= strchr(*ap
++ = add (buffer
, NULL
), '=');
1386 * Skip the output of Content-Type, parameters, content
1387 * description and disposition, and Content-ID if the
1388 * content is of type "message" and the rfc934 compatibility
1389 * flag is set (which means we are inside multipart/digest
1390 * and the switch -rfc934mode was given).
1392 if (ct
->c_type
== CT_MESSAGE
&& ct
->c_rfc934
)
1396 * output the content type and subtype
1398 np
= add (TYPE_FIELD
, NULL
);
1399 vp
= concat (" ", ci
->ci_type
, "/", ci
->ci_subtype
, NULL
);
1401 /* keep track of length of line */
1402 len
= strlen (TYPE_FIELD
) + strlen (ci
->ci_type
)
1403 + strlen (ci
->ci_subtype
) + 3;
1405 mailbody
= ct
->c_type
== CT_MESSAGE
1406 && ct
->c_subtype
== MESSAGE_EXTERNAL
1407 && ((struct exbody
*) ct
->c_ctparams
)->eb_body
;
1410 * Append the attribute/value pairs to
1411 * the end of the Content-Type line.
1413 for (ap
= ci
->ci_attrs
, ep
= ci
->ci_values
; *ap
; ap
++, ep
++) {
1414 if (mailbody
&& !mh_strcasecmp (*ap
, "body"))
1420 snprintf (buffer
, sizeof(buffer
), "%s=\"%s\"", *ap
, *ep
);
1421 if (len
+ 1 + (cc
= strlen (buffer
)) >= CPERLIN
) {
1422 vp
= add ("\n\t", vp
);
1428 vp
= add (buffer
, vp
);
1433 * Append any RFC-822 comment to the end of
1434 * the Content-Type line.
1436 if (ci
->ci_comment
) {
1437 snprintf (buffer
, sizeof(buffer
), "(%s)", ci
->ci_comment
);
1438 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
1439 vp
= add ("\n\t", vp
);
1445 vp
= add (buffer
, vp
);
1448 vp
= add ("\n", vp
);
1449 add_header (ct
, np
, vp
);
1452 * output the Content-ID, unless disabled by -nocontentid
1454 if (contentidsw
&& ct
->c_id
) {
1455 np
= add (ID_FIELD
, NULL
);
1456 vp
= concat (" ", ct
->c_id
, NULL
);
1457 add_header (ct
, np
, vp
);
1461 * output the Content-Description
1464 np
= add (DESCR_FIELD
, NULL
);
1465 vp
= concat (" ", ct
->c_descr
, NULL
);
1466 add_header (ct
, np
, vp
);
1470 * output the Content-Disposition
1473 np
= add (DISPO_FIELD
, NULL
);
1474 vp
= concat (" ", ct
->c_dispo
, NULL
);
1475 add_header (ct
, np
, vp
);
1480 * If this is the internal content structure for a
1481 * "message/external", then we are done with the
1482 * headers (since it has no body).
1488 * output the Content-MD5
1491 np
= add (MD5_FIELD
, NULL
);
1492 vp
= calculate_digest (ct
, (ct
->c_encoding
== CE_QUOTED
) ? 1 : 0);
1493 add_header (ct
, np
, vp
);
1497 * output the Content-Transfer-Encoding
1499 switch (ct
->c_encoding
) {
1501 /* Nothing to output */
1505 if (ct
->c_type
== CT_MESSAGE
)
1506 adios (NULL
, "internal error, invalid encoding");
1508 np
= add (ENCODING_FIELD
, NULL
);
1509 vp
= concat (" ", "8bit", "\n", NULL
);
1510 add_header (ct
, np
, vp
);
1514 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1515 adios (NULL
, "internal error, invalid encoding");
1517 np
= add (ENCODING_FIELD
, NULL
);
1518 vp
= concat (" ", "quoted-printable", "\n", NULL
);
1519 add_header (ct
, np
, vp
);
1523 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1524 adios (NULL
, "internal error, invalid encoding");
1526 np
= add (ENCODING_FIELD
, NULL
);
1527 vp
= concat (" ", "base64", "\n", NULL
);
1528 add_header (ct
, np
, vp
);
1532 if (ct
->c_type
== CT_MESSAGE
)
1533 adios (NULL
, "internal error, invalid encoding");
1535 np
= add (ENCODING_FIELD
, NULL
);
1536 vp
= concat (" ", "binary", "\n", NULL
);
1537 add_header (ct
, np
, vp
);
1541 adios (NULL
, "unknown transfer encoding in content");
1546 * Additional content specific header processing
1548 switch (ct
->c_type
) {
1551 struct multipart
*m
;
1554 m
= (struct multipart
*) ct
->c_ctparams
;
1555 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1565 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1568 e
= (struct exbody
*) ct
->c_ctparams
;
1569 build_headers (e
->eb_content
);
1582 static char nib2b64
[0x40+1] =
1583 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1586 calculate_digest (CT ct
, int asciiP
)
1589 char buffer
[BUFSIZ
], *vp
, *op
;
1591 unsigned char digest
[16];
1592 unsigned char outbuf
[25];
1594 CE ce
= ct
->c_cefile
;
1595 char *infilename
= ce
->ce_file
? ce
->ce_file
: ct
->c_file
;
1599 if ((in
= fopen (infilename
, "r")) == NULL
)
1600 adios (infilename
, "unable to open for reading");
1602 /* Initialize md5 context */
1603 MD5Init (&mdContext
);
1605 /* calculate md5 message digest */
1607 while (fgets (buffer
, sizeof(buffer
) - 1, in
)) {
1610 cp
= buffer
+ strlen (buffer
) - 1;
1611 if ((c
= *cp
) == '\n')
1614 MD5Update (&mdContext
, (unsigned char *) buffer
,
1615 (unsigned int) strlen (buffer
));
1618 MD5Update (&mdContext
, (unsigned char *) "\r\n", 2);
1621 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), in
)) > 0)
1622 MD5Update (&mdContext
, (unsigned char *) buffer
, (unsigned int) cc
);
1625 /* md5 finalization. Write digest and zero md5 context */
1626 MD5Final (digest
, &mdContext
);
1631 /* print debugging info */
1635 fprintf (stderr
, "MD5 digest=");
1636 for (ep
= (dp
= digest
) + sizeof(digest
) / sizeof(digest
[0]);
1638 fprintf (stderr
, "%02x", *dp
& 0xff);
1639 fprintf (stderr
, "\n");
1642 /* encode the digest using base64 */
1643 for (dp
= digest
, op
= (char *) outbuf
,
1644 cc
= sizeof(digest
) / sizeof(digest
[0]);
1645 cc
> 0; cc
-= 3, op
+= 4) {
1649 bits
= (*dp
++ & 0xff) << 16;
1651 bits
|= (*dp
++ & 0xff) << 8;
1653 bits
|= *dp
++ & 0xff;
1656 for (bp
= op
+ 4; bp
> op
; bits
>>= 6)
1657 *--bp
= nib2b64
[bits
& 0x3f];
1665 /* null terminate string */
1668 /* now make copy and return string */
1669 vp
= concat (" ", outbuf
, "\n", NULL
);