3 * mhbuildsbr.c -- routines to expand/translate MIME composition files
7 * This code is Copyright (c) 2002, by the authors of nmh. See the
8 * COPYRIGHT file in the root directory of the nmh distribution for
9 * complete copyright information.
13 * This code was originally part of mhn.c. I split it into
14 * a separate program (mhbuild.c) and then later split it
15 * again (mhbuildsbr.c). But the code still has some of
16 * the mhn.c code in it. This program needs additional
17 * streamlining and removal of unneeded code.
22 #include <h/signals.h>
29 #include <h/mhparse.h>
32 #ifdef TIME_WITH_SYS_TIME
33 # include <sys/time.h>
36 # ifdef TM_IN_SYS_TIME
37 # include <sys/time.h>
43 #ifdef HAVE_SYS_WAIT_H
44 # include <sys/wait.h>
54 extern int contentidsw;
56 extern int endian; /* mhmisc.c */
59 extern int rcachesw; /* mhcachesbr.c */
60 extern int wcachesw; /* mhcachesbr.c */
63 * Directory to place tmp files. This must
64 * be set before these routines are called.
70 static char prefix[] = "----- =_aaaaaaaaaa";
74 int make_intermediates (char *);
75 void content_error (char *, CT, char *, ...);
78 int find_cache (CT, int, int *, char *, char *, int);
81 int ftp_get (char *, char *, char *, char *, char *, char *, int, int);
84 void free_content (CT);
85 void free_ctinfo (CT);
86 void free_encoding (CT, int);
91 CT build_mime (char *);
96 static int init_decoded_content (CT);
97 static char *fgetstr (char *, int, FILE *);
98 static int user_content (FILE *, char *, char *, CT *);
99 static void set_id (CT, int);
100 static int compose_content (CT);
101 static int scan_content (CT);
102 static int build_headers (CT);
103 static char *calculate_digest (CT, int);
107 * Main routine for translating composition file
108 * into valid MIME message. It translates the draft
109 * into a content structure (actually a tree of content
110 * structures). This message then can be manipulated
111 * in various ways, including being output via
116 build_mime (char *infile)
119 char buf[BUFSIZ], name[NAMESZ];
126 umask (~m_gmprot ());
128 /* open the composition draft */
129 if ((in = fopen (infile, "r")) == NULL)
130 adios (infile, "unable to open for reading");
133 * Allocate space for primary (outside) content
135 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
136 adios (NULL, "out of memory");
139 * Allocate structure for handling decoded content
140 * for this part. We don't really need this, but
141 * allocate it to remain consistent.
143 init_decoded_content (ct);
146 * Parse some of the header fields in the composition
147 * draft into the linked list of header fields for
148 * the new MIME message.
150 for (compnum = 1, state = FLD;;) {
151 switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
157 /* abort if draft has Mime-Version header field */
158 if (!mh_strcasecmp (name, VRSN_FIELD))
159 adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
161 /* abort if draft has Content-Transfer-Encoding header field */
162 if (!mh_strcasecmp (name, ENCODING_FIELD))
163 adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
165 /* ignore any Content-Type fields in the header */
166 if (!mh_strcasecmp (name, TYPE_FIELD)) {
167 while (state == FLDPLUS)
168 state = m_getfld (state, name, buf, sizeof(buf), in);
172 /* get copies of the buffers */
173 np = add (name, NULL);
174 vp = add (buf, NULL);
176 /* if necessary, get rest of field */
177 while (state == FLDPLUS) {
178 state = m_getfld (state, name, buf, sizeof(buf), in);
179 vp = add (buf, vp); /* add to previous value */
182 /* Now add the header data to the list */
183 add_header (ct, np, vp);
186 /* if this wasn't the last header field, then continue */
192 adios (NULL, "draft has empty body -- no directives!");
197 fseek (in, (long) (-strlen (buf)), SEEK_CUR);
202 adios (NULL, "message format error in component #%d", compnum);
205 adios (NULL, "getfld() returned %d", state);
211 * Now add the MIME-Version header field
212 * to the list of header fields.
214 np = add (VRSN_FIELD, NULL);
215 vp = concat (" ", VRSN_VALUE, "\n", NULL);
216 add_header (ct, np, vp);
219 * We initally assume we will find multiple contents in the
220 * draft. So create a multipart/mixed content to hold everything.
221 * We can remove this later, if it is not needed.
223 if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
225 ct->c_type = CT_MULTIPART;
226 ct->c_subtype = MULTI_MIXED;
227 ct->c_file = add (infile, NULL);
229 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
230 adios (NULL, "out of memory");
231 ct->c_ctparams = (void *) m;
235 * read and parse the composition file
236 * and the directives it contains.
238 while (fgetstr (buf, sizeof(buf) - 1, in)) {
242 if (user_content (in, infile, buf, &p) == DONE) {
243 admonish (NULL, "ignoring spurious #end");
249 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
250 adios (NULL, "out of memory");
257 * close the composition draft since
258 * it's not needed any longer.
262 /* check if any contents were found */
264 adios (NULL, "no content directives found");
267 * If only one content was found, then remove and
268 * free the outer multipart content.
270 if (!m->mp_parts->mp_next) {
273 p = m->mp_parts->mp_part;
274 m->mp_parts->mp_part = NULL;
276 /* move header fields */
277 p->c_first_hf = ct->c_first_hf;
278 p->c_last_hf = ct->c_last_hf;
279 ct->c_first_hf = NULL;
280 ct->c_last_hf = NULL;
289 * Fill out, or expand directives. Parse and execute
290 * commands specified by profile composition strings.
292 compose_content (ct);
294 if ((cp = strchr(prefix, 'a')) == NULL)
295 adios (NULL, "internal error(4)");
298 * Scan the contents. Choose a transfer encoding, and
299 * check if prefix for multipart boundary clashes with
300 * any of the contents.
302 while (scan_content (ct) == NOTOK) {
307 adios (NULL, "giving up trying to find a unique delimiter string");
313 /* Build the rest of the header field structures */
321 * Set up structures for placing unencoded
322 * content when building parts.
326 init_decoded_content (CT ct)
330 if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
331 adios (NULL, "out of memory");
334 ct->c_ceopenfnx = open7Bit; /* since unencoded */
335 ct->c_ceclosefnx = close_encoding;
336 ct->c_cesizefnx = NULL; /* since unencoded */
343 fgetstr (char *s, int n, FILE *stream)
347 for (ep = (cp = s) + n; cp < ep; ) {
350 if (!fgets (cp, n, stream))
351 return (cp != s ? s : NULL);
352 if (cp == s && *cp != '#')
355 cp += (i = strlen (cp)) - 1;
356 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
367 * Parse the composition draft for text and directives.
368 * Do initial setup of Content structure.
372 user_content (FILE *in, char *file, char *buf, CT *ctp)
381 struct str2init *s2i;
386 if (buf[0] == '\n' || strcmp (buf, "#\n") == 0) {
391 /* allocate basic Content structure */
392 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
393 adios (NULL, "out of memory");
396 /* allocate basic structure for handling decoded content */
397 init_decoded_content (ct);
404 * Handle inline text. Check if line
405 * is one of the following forms:
407 * 1) doesn't begin with '#' (implicit directive)
408 * 2) begins with "##" (implicit directive)
409 * 3) begins with "#<"
411 if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
415 char content[BUFSIZ];
418 /* use a temp file to collect the plain text lines */
419 ce->ce_file = add (m_tmpfil (invo_name), NULL);
422 if ((out = fopen (ce->ce_file, "w")) == NULL)
423 adios (ce->ce_file, "unable to open for writing");
425 if (buf[0] == '#' && buf[1] == '<') {
426 strncpy (content, buf + 2, sizeof(content));
433 /* the directive is implicit */
434 strncpy (content, "text/plain", sizeof(content));
436 strncpy (buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
440 if (headers >= 0 && uprf (buffer, DESCR_FIELD)
441 && buffer[i = strlen (DESCR_FIELD)] == ':') {
445 ct->c_descr = add (buffer + i + 1, ct->c_descr);
446 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
447 adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
455 adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
463 if (headers >= 0 && uprf (buffer, DISPO_FIELD)
464 && buffer[i = strlen (DISPO_FIELD)] == ':') {
468 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
469 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
470 adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
478 adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
486 if (headers != 1 || buffer[0] != '\n')
492 if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
494 if (buffer[0] == '#') {
497 if (buffer[1] != '#')
499 for (cp = (bp = buffer) + 1; *cp; cp++)
506 ct->c_end = ftell (out);
509 /* parse content type */
510 if (get_ctinfo (content, ct, inlineD) == NOTOK)
513 for (s2i = str2cts; s2i->si_key; s2i++)
514 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
516 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
520 * check type specified (possibly implicitly)
522 switch (ct->c_type = s2i->si_val) {
524 if (!mh_strcasecmp (ci->ci_subtype, "rfc822")) {
525 ct->c_encoding = CE_7BIT;
530 adios (NULL, "it doesn't make sense to define an in-line %s content",
531 ct->c_type == CT_MESSAGE ? "message" : "multipart");
536 if ((ct->c_ctinitfnx = s2i->si_init))
537 (*ct->c_ctinitfnx) (ct);
542 fseek (in, pos, SEEK_SET);
547 * If we've reached this point, the next line
548 * must be some type of explicit directive.
551 /* check if directive is external-type */
552 extrnal = (buf[1] == '@');
554 /* parse directive */
555 if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
558 /* check directive against the list of MIME types */
559 for (s2i = str2cts; s2i->si_key; s2i++)
560 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
564 * Check if the directive specified a valid type.
565 * This will happen if it was one of the following forms:
572 adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
574 switch (ct->c_type = s2i->si_val) {
576 adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
577 ci->ci_type, ci->ci_subtype);
581 if (!mh_strcasecmp (ci->ci_subtype, "partial"))
582 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
583 ci->ci_type, ci->ci_subtype);
584 if (!mh_strcasecmp (ci->ci_subtype, "external-body"))
585 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
586 ci->ci_type, ci->ci_subtype);
589 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
590 ci->ci_type, ci->ci_subtype);
594 if ((ct->c_ctinitfnx = s2i->si_init))
595 (*ct->c_ctinitfnx) (ct);
600 * #@type/subtype (external types directive)
607 adios (NULL, "need external information for \"#@%s/%s\"",
608 ci->ci_type, ci->ci_subtype);
611 snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
616 * Since we are using the current Content structure to
617 * hold information about the type of the external
618 * reference, we need to create another Content structure
619 * for the message/external-body to wrap it in.
621 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
622 adios (NULL, "out of memory");
625 if (get_ctinfo (buffer, ct, 0) == NOTOK)
627 ct->c_type = CT_MESSAGE;
628 ct->c_subtype = MESSAGE_EXTERNAL;
630 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
631 adios (NULL, "out of memory");
632 ct->c_ctparams = (void *) e;
638 if (params_external (ct, 1) == NOTOK)
644 /* Handle [file] argument */
646 /* check if specifies command to execute */
647 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
648 for (cp = ci->ci_magic + 1; isspace (*cp); cp++)
651 adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
656 /* record filename of decoded contents */
657 ce->ce_file = ci->ci_magic;
658 if (access (ce->ce_file, R_OK) == NOTOK)
659 adios ("reading", "unable to access %s for", ce->ce_file);
660 if (listsw && stat (ce->ce_file, &st) != NOTOK)
661 ct->c_end = (long) st.st_size;
668 * No [file] argument, so check profile for
669 * method to compose content.
671 snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
672 invo_name, ci->ci_type, ci->ci_subtype);
673 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
674 snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type);
675 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
676 content_error (NULL, ct, "don't know how to compose content");
680 ci->ci_magic = add (cp, NULL);
685 adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
689 * #forw [+folder] [msgs]
691 if (!mh_strcasecmp (ci->ci_type, "forw")) {
693 char *folder, *arguments[MAXARGS];
697 ap = brkstring (ci->ci_magic, " ", "\n");
698 copyip (ap, arguments, MAXARGS);
700 arguments[0] = "cur";
705 /* search the arguments for a folder name */
706 for (ap = arguments; *ap; ap++) {
708 if (*cp == '+' || *cp == '@') {
710 adios (NULL, "only one folder per #forw directive");
712 folder = pluspath (cp);
716 /* else, use the current folder */
718 folder = add (getfolder (1), NULL);
720 if (!(mp = folder_read (folder)))
721 adios (NULL, "unable to read folder %s", folder);
722 for (ap = arguments; *ap; ap++) {
724 if (*cp != '+' && *cp != '@')
725 if (!m_convert (mp, cp))
732 * If there is more than one message to include, make this
733 * a content of type "multipart/digest" and insert each message
734 * as a subpart. If there is only one message, then make this
735 * a content of type "message/rfc822".
737 if (mp->numsel > 1) {
738 /* we are forwarding multiple messages */
739 if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
741 ct->c_type = CT_MULTIPART;
742 ct->c_subtype = MULTI_DIGEST;
744 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
745 adios (NULL, "out of memory");
746 ct->c_ctparams = (void *) m;
749 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
750 if (is_selected(mp, msgnum)) {
755 if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
756 adios (NULL, "out of memory");
757 init_decoded_content (p);
759 if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
761 p->c_type = CT_MESSAGE;
762 p->c_subtype = MESSAGE_RFC822;
764 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
765 pe->ce_file = add (buffer, NULL);
766 if (listsw && stat (pe->ce_file, &st) != NOTOK)
767 p->c_end = (long) st.st_size;
769 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
770 adios (NULL, "out of memory");
777 /* we are forwarding one message */
778 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
780 ct->c_type = CT_MESSAGE;
781 ct->c_subtype = MESSAGE_RFC822;
784 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
785 ce->ce_file = add (buffer, NULL);
786 if (listsw && stat (ce->ce_file, &st) != NOTOK)
787 ct->c_end = (long) st.st_size;
790 folder_free (mp); /* free folder/message structure */
797 if (!mh_strcasecmp (ci->ci_type, "end")) {
804 * #begin [ alternative | parallel ]
806 if (!mh_strcasecmp (ci->ci_type, "begin")) {
809 cp = SubMultiPart[vrsn - 1].kv_key;
810 } else if (!mh_strcasecmp (ci->ci_magic, "alternative")) {
811 vrsn = MULTI_ALTERNATE;
812 cp = SubMultiPart[vrsn - 1].kv_key;
813 } else if (!mh_strcasecmp (ci->ci_magic, "parallel")) {
814 vrsn = MULTI_PARALLEL;
815 cp = SubMultiPart[vrsn - 1].kv_key;
816 } else if (uprf (ci->ci_magic, "digest")) {
819 vrsn = MULTI_UNKNOWN;
824 snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
825 if (get_ctinfo (buffer, ct, 0) == NOTOK)
827 ct->c_type = CT_MULTIPART;
828 ct->c_subtype = vrsn;
830 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
831 adios (NULL, "out of memory");
832 ct->c_ctparams = (void *) m;
835 while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
839 if (user_content (in, file, buffer, &p) == DONE) {
841 adios (NULL, "empty \"#begin ... #end\" sequence");
847 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
848 adios (NULL, "out of memory");
853 admonish (NULL, "premature end-of-file, missing #end");
860 adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
861 return NOTOK; /* NOT REACHED */
866 set_id (CT ct, int top)
870 static time_t clock = 0;
875 snprintf (msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
876 (int) getpid(), (long) clock, LocalName());
878 msgfmt = getcpy(msgid);
880 snprintf (msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
881 ct->c_id = getcpy (msgid);
885 static char ebcdicsafe[0x100] = {
886 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
887 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
888 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
889 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
890 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
891 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
892 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
893 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
894 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
895 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
896 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
897 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
898 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
899 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
900 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
901 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
909 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
910 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
911 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
913 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
914 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
915 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
916 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
917 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
922 * Fill out, or expand the various contents in the composition
923 * draft. Read-in any necessary files. Parse and execute any
924 * commands specified by profile composition strings.
928 compose_content (CT ct)
930 CE ce = ct->c_cefile;
932 switch (ct->c_type) {
937 char partnam[BUFSIZ];
938 struct multipart *m = (struct multipart *) ct->c_ctparams;
942 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
943 pp = partnam + strlen (partnam);
948 /* first, we call compose_content on all the subparts */
949 for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
950 CT p = part->mp_part;
952 sprintf (pp, "%d", partnum);
953 p->c_partno = add (partnam, NULL);
954 if (compose_content (p) == NOTOK)
959 * If the -rfc934mode switch is given, then check all
960 * the subparts of a multipart/digest. If they are all
961 * message/rfc822, then mark this content and all
962 * subparts with the rfc934 compatibility mode flag.
964 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
967 for (part = m->mp_parts; part; part = part->mp_next) {
968 CT p = part->mp_part;
970 if (p->c_subtype != MESSAGE_RFC822) {
975 ct->c_rfc934 = is934;
976 for (part = m->mp_parts; part; part = part->mp_next) {
977 CT p = part->mp_part;
979 if ((p->c_rfc934 = is934))
985 ct->c_end = (partnum = strlen (prefix) + 2) + 2;
989 for (part = m->mp_parts; part; part = part->mp_next)
990 ct->c_end += part->mp_part->c_end + partnum;
996 /* Nothing to do for type message */
1000 * Discrete types (text/application/audio/image/video)
1005 int i, xstdout, len, buflen;
1006 char *bp, **ap, *cp;
1007 char *vec[4], buffer[BUFSIZ];
1009 CI ci = &ct->c_ctinfo;
1011 if (!(cp = ci->ci_magic))
1012 adios (NULL, "internal error(5)");
1014 ce->ce_file = add (m_tmpfil (invo_name), NULL);
1019 /* Get buffer ready to go */
1022 buflen = sizeof(buffer);
1025 * Parse composition string into buffer
1027 for ( ; *cp; cp++) {
1032 /* insert parameters from directive */
1036 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1037 snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1047 /* %f, and stdout is not-redirected */
1053 * insert temporary filename where
1054 * content should be written
1056 snprintf (bp, buflen, "%s", ce->ce_file);
1060 /* insert content subtype */
1061 strncpy (bp, ci->ci_subtype, buflen);
1065 /* insert character % */
1086 printf ("composing content %s/%s from command\n\t%s\n",
1087 ci->ci_type, ci->ci_subtype, buffer);
1089 fflush (stdout); /* not sure if need for -noverbose */
1096 if ((out = fopen (ce->ce_file, "w")) == NULL)
1097 adios (ce->ce_file, "unable to open for writing");
1099 for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++)
1103 adios ("fork", "unable to fork");
1108 dup2 (fileno (out), 1);
1109 close (fileno (out));
1110 execvp ("/bin/sh", vec);
1111 fprintf (stderr, "unable to exec ");
1118 if (pidXwait(child_id, NULL))
1124 /* Check size of file */
1125 if (listsw && ct->c_end == 0L) {
1128 if (stat (ce->ce_file, &st) != NOTOK)
1129 ct->c_end = (long) st.st_size;
1141 * 1) choose a transfer encoding.
1142 * 2) check for clashes with multipart boundary string.
1143 * 3) for text content, figure out which character set is being used.
1145 * If there is a clash with one of the contents and the multipart boundary,
1146 * this function will exit with NOTOK. This will cause the scanning process
1147 * to be repeated with a different multipart boundary. It is possible
1148 * (although highly unlikely) that this scan will be repeated multiple times.
1152 scan_content (CT ct)
1155 int check8bit, contains8bit = 0; /* check if contains 8bit data */
1156 int checklinelen, linelen = 0; /* check for long lines */
1157 int checkboundary, boundaryclash = 0; /* check if clashes with multipart boundary */
1158 int checklinespace, linespace = 0; /* check if any line ends with space */
1159 int checkebcdic, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
1160 unsigned char *cp, buffer[BUFSIZ];
1163 CE ce = ct->c_cefile;
1166 * handle multipart by scanning all subparts
1167 * and then checking their encoding.
1169 if (ct->c_type == CT_MULTIPART) {
1170 struct multipart *m = (struct multipart *) ct->c_ctparams;
1173 /* initially mark the domain of enclosing multipart as 7bit */
1174 ct->c_encoding = CE_7BIT;
1176 for (part = m->mp_parts; part; part = part->mp_next) {
1177 CT p = part->mp_part;
1179 if (scan_content (p) == NOTOK) /* choose encoding for subpart */
1182 /* if necessary, enlarge encoding for enclosing multipart */
1183 if (p->c_encoding == CE_BINARY)
1184 ct->c_encoding = CE_BINARY;
1185 if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
1186 ct->c_encoding = CE_8BIT;
1193 * Decide what to check while scanning this content.
1195 switch (ct->c_type) {
1199 if (ct->c_subtype == TEXT_PLAIN) {
1204 checkebcdic = ebcdicsw;
1210 case CT_APPLICATION:
1212 checkebcdic = ebcdicsw;
1224 /* don't check anything for message/external */
1225 if (ct->c_subtype == MESSAGE_EXTERNAL)
1235 * Don't check anything for these types,
1236 * since we are forcing use of base64.
1247 * Scan the unencoded content
1249 if (check8bit || checklinelen || checklinespace || checkboundary) {
1250 if ((in = fopen (ce->ce_file, "r")) == NULL)
1251 adios (ce->ce_file, "unable to open for reading");
1252 len = strlen (prefix);
1254 while (fgets (buffer, sizeof(buffer) - 1, in)) {
1256 * Check for 8bit data.
1259 for (cp = buffer; *cp; cp++) {
1260 if (!isascii (*cp)) {
1262 check8bit = 0; /* no need to keep checking */
1265 * Check if character is ebcdic-safe. We only check
1266 * this if also checking for 8bit data.
1268 if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1270 checkebcdic = 0; /* no need to keep checking */
1276 * Check line length.
1278 if (checklinelen && (strlen (buffer) > CPERLIN + 1)) {
1280 checklinelen = 0; /* no need to keep checking */
1284 * Check if line ends with a space.
1286 if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace (*cp)) {
1288 checklinespace = 0; /* no need to keep checking */
1292 * Check if content contains a line that clashes
1293 * with our standard boundary for multipart messages.
1295 if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
1296 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
1300 if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) {
1302 checkboundary = 0; /* no need to keep checking */
1310 * Decide which transfer encoding to use.
1312 switch (ct->c_type) {
1315 * If the text content didn't specify a character
1316 * set, we need to figure out which one was used.
1318 t = (struct text *) ct->c_ctparams;
1319 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1320 CI ci = &ct->c_ctinfo;
1323 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1327 t->tx_charset = CHARSET_UNKNOWN;
1328 *ap = concat ("charset=", write_charset_8bit(), NULL);
1330 t->tx_charset = CHARSET_USASCII;
1331 *ap = add ("charset=us-ascii", NULL);
1334 cp = strchr(*ap++, '=');
1340 if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1341 ct->c_encoding = CE_QUOTED;
1343 ct->c_encoding = CE_7BIT;
1346 case CT_APPLICATION:
1347 /* For application type, use base64, except when postscript */
1348 if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1349 ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
1350 ? CE_QUOTED : CE_BASE64;
1352 ct->c_encoding = CE_7BIT;
1356 ct->c_encoding = CE_7BIT;
1362 /* For audio, image, and video contents, just use base64 */
1363 ct->c_encoding = CE_BASE64;
1367 return (boundaryclash ? NOTOK : OK);
1372 * Scan the content structures, and build header
1373 * fields that will need to be output into the
1378 build_headers (CT ct)
1380 int cc, mailbody, len;
1382 char *np, *vp, buffer[BUFSIZ];
1383 CI ci = &ct->c_ctinfo;
1386 * If message is type multipart, then add the multipart
1387 * boundary to the list of attribute/value pairs.
1389 if (ct->c_type == CT_MULTIPART) {
1391 static int level = 0; /* store nesting level */
1395 snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++);
1396 cp = strchr(*ap++ = add (buffer, NULL), '=');
1403 * Skip the output of Content-Type, parameters, content
1404 * description and disposition, and Content-ID if the
1405 * content is of type "message" and the rfc934 compatibility
1406 * flag is set (which means we are inside multipart/digest
1407 * and the switch -rfc934mode was given).
1409 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1413 * output the content type and subtype
1415 np = add (TYPE_FIELD, NULL);
1416 vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1418 /* keep track of length of line */
1419 len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
1420 + strlen (ci->ci_subtype) + 3;
1422 mailbody = ct->c_type == CT_MESSAGE
1423 && ct->c_subtype == MESSAGE_EXTERNAL
1424 && ((struct exbody *) ct->c_ctparams)->eb_body;
1427 * Append the attribute/value pairs to
1428 * the end of the Content-Type line.
1430 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1431 if (mailbody && !mh_strcasecmp (*ap, "body"))
1437 snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1438 if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
1439 vp = add ("\n\t", vp);
1445 vp = add (buffer, vp);
1450 * Append any RFC-822 comment to the end of
1451 * the Content-Type line.
1453 if (ci->ci_comment) {
1454 snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1455 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
1456 vp = add ("\n\t", vp);
1462 vp = add (buffer, vp);
1465 vp = add ("\n", vp);
1466 add_header (ct, np, vp);
1469 * output the Content-ID, unless disabled by -nocontentid
1471 if (contentidsw && ct->c_id) {
1472 np = add (ID_FIELD, NULL);
1473 vp = concat (" ", ct->c_id, NULL);
1474 add_header (ct, np, vp);
1478 * output the Content-Description
1481 np = add (DESCR_FIELD, NULL);
1482 vp = concat (" ", ct->c_descr, NULL);
1483 add_header (ct, np, vp);
1487 * output the Content-Disposition
1490 np = add (DISPO_FIELD, NULL);
1491 vp = concat (" ", ct->c_dispo, NULL);
1492 add_header (ct, np, vp);
1497 * If this is the internal content structure for a
1498 * "message/external", then we are done with the
1499 * headers (since it has no body).
1505 * output the Content-MD5
1508 np = add (MD5_FIELD, NULL);
1509 vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
1510 add_header (ct, np, vp);
1514 * output the Content-Transfer-Encoding
1516 switch (ct->c_encoding) {
1518 /* Nothing to output */
1520 np = add (ENCODING_FIELD, NULL);
1521 vp = concat (" ", "7bit", "\n", NULL);
1522 add_header (ct, np, vp);
1527 if (ct->c_type == CT_MESSAGE)
1528 adios (NULL, "internal error, invalid encoding");
1530 np = add (ENCODING_FIELD, NULL);
1531 vp = concat (" ", "8bit", "\n", NULL);
1532 add_header (ct, np, vp);
1536 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1537 adios (NULL, "internal error, invalid encoding");
1539 np = add (ENCODING_FIELD, NULL);
1540 vp = concat (" ", "quoted-printable", "\n", NULL);
1541 add_header (ct, np, vp);
1545 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1546 adios (NULL, "internal error, invalid encoding");
1548 np = add (ENCODING_FIELD, NULL);
1549 vp = concat (" ", "base64", "\n", NULL);
1550 add_header (ct, np, vp);
1554 if (ct->c_type == CT_MESSAGE)
1555 adios (NULL, "internal error, invalid encoding");
1557 np = add (ENCODING_FIELD, NULL);
1558 vp = concat (" ", "binary", "\n", NULL);
1559 add_header (ct, np, vp);
1563 adios (NULL, "unknown transfer encoding in content");
1568 * Additional content specific header processing
1570 switch (ct->c_type) {
1573 struct multipart *m;
1576 m = (struct multipart *) ct->c_ctparams;
1577 for (part = m->mp_parts; part; part = part->mp_next) {
1587 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1590 e = (struct exbody *) ct->c_ctparams;
1591 build_headers (e->eb_content);
1604 static char nib2b64[0x40+1] =
1605 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1608 calculate_digest (CT ct, int asciiP)
1611 char buffer[BUFSIZ], *vp, *op;
1613 unsigned char digest[16];
1614 unsigned char outbuf[25];
1617 CE ce = ct->c_cefile;
1620 if ((in = fopen (ce->ce_file, "r")) == NULL)
1621 adios (ce->ce_file, "unable to open for reading");
1623 /* Initialize md5 context */
1624 MD5Init (&mdContext);
1626 /* calculate md5 message digest */
1628 while (fgets (buffer, sizeof(buffer) - 1, in)) {
1631 cp = buffer + strlen (buffer) - 1;
1632 if ((c = *cp) == '\n')
1635 MD5Update (&mdContext, (unsigned char *) buffer,
1636 (unsigned int) strlen (buffer));
1639 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
1642 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
1643 MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
1646 /* md5 finalization. Write digest and zero md5 context */
1647 MD5Final (digest, &mdContext);
1652 /* print debugging info */
1656 fprintf (stderr, "MD5 digest=");
1657 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1659 fprintf (stderr, "%02x", *dp & 0xff);
1660 fprintf (stderr, "\n");
1663 /* encode the digest using base64 */
1664 for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
1665 cc > 0; cc -= 3, op += 4) {
1669 bits = (*dp++ & 0xff) << 16;
1671 bits |= (*dp++ & 0xff) << 8;
1673 bits |= *dp++ & 0xff;
1676 for (bp = op + 4; bp > op; bits >>= 6)
1677 *--bp = nib2b64[bits & 0x3f];
1685 /* null terminate string */
1688 /* now make copy and return string */
1689 vp = concat (" ", outbuf, "\n", NULL);