2 ** mhbuildsbr.c -- routines to expand/translate MIME composition files
4 ** This code is Copyright (c) 2002, by the authors of nmh. See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
10 ** This code was originally part of mhn.c. I split it into
11 ** a separate program (mhbuild.c) and then later split it
12 ** again (mhbuildsbr.c). But the code still has some of
13 ** the mhn.c code in it. This program needs additional
14 ** streamlining and removal of unneeded code.
19 #include <h/signals.h>
25 #include <h/mhparse.h>
28 #ifdef TIME_WITH_SYS_TIME
29 # include <sys/time.h>
32 # ifdef TM_IN_SYS_TIME
33 # include <sys/time.h>
39 #ifdef HAVE_SYS_WAIT_H
40 # include <sys/wait.h>
50 extern int contentidsw;
52 extern int endian; /* mhmisc.c */
55 extern int rcachesw; /* mhcachesbr.c */
56 extern int wcachesw; /* mhcachesbr.c */
59 ** Directory to place tmp files. This must
60 ** be set before these routines are called.
66 static char prefix[] = "----- =_aaaaaaaaaa";
70 int make_intermediates(char *);
71 void content_error(char *, CT, char *, ...);
74 int find_cache(CT, int, int *, char *, char *, int);
77 int ftp_get(char *, char *, char *, char *, char *, char *, int, int);
80 void free_content(CT);
82 void free_encoding(CT, int);
87 CT build_mime(char *);
92 static int init_decoded_content(CT);
93 static char *fgetstr(char *, int, FILE *);
94 static int user_content(FILE *, char *, char *, CT *);
95 static void set_id(CT, int);
96 static int compose_content(CT);
97 static int scan_content(CT);
98 static int build_headers(CT);
99 static char *calculate_digest(CT, int);
103 ** Main routine for translating composition file
104 ** into valid MIME message. It translates the draft
105 ** into a content structure (actually a tree of content
106 ** structures). This message then can be manipulated
107 ** in various ways, including being output via
112 build_mime(char *infile)
115 char buf[BUFSIZ], name[NAMESZ];
124 /* open the composition draft */
125 if ((in = fopen(infile, "r")) == NULL)
126 adios(infile, "unable to open for reading");
129 ** Allocate space for primary (outside) content
131 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
132 adios(NULL, "out of memory");
135 ** Allocate structure for handling decoded content
136 ** for this part. We don't really need this, but
137 ** allocate it to remain consistent.
139 init_decoded_content(ct);
142 ** Parse some of the header fields in the composition
143 ** draft into the linked list of header fields for
144 ** the new MIME message.
146 for (compnum = 1, state = FLD;;) {
147 switch (state = m_getfld(state, name, buf, sizeof(buf), in)) {
153 /* abort if draft has Mime-Version header field */
154 if (!mh_strcasecmp(name, VRSN_FIELD))
155 adios(NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
158 ** abort if draft has Content-Transfer-Encoding
161 if (!mh_strcasecmp(name, ENCODING_FIELD))
162 adios(NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
164 /* ignore any Content-Type fields in the header */
165 if (!mh_strcasecmp(name, TYPE_FIELD)) {
166 while (state == FLDPLUS)
167 state = m_getfld(state, name, buf,
172 /* get copies of the buffers */
176 /* if necessary, get rest of field */
177 while (state == FLDPLUS) {
178 state = m_getfld(state, name, buf,
180 vp = add(buf, vp); /* add to prev value */
183 /* Now add the header data to the list */
184 add_header(ct, np, vp);
187 /* if this wasn't the last hdr field, then continue */
193 adios(NULL, "draft has empty body -- no directives!");
198 fseek(in, (long) (-strlen(buf)), SEEK_CUR);
203 adios(NULL, "message format error in component #%d",
207 adios(NULL, "getfld() returned %d", state);
213 ** Now add the MIME-Version header field
214 ** to the list of header fields.
216 np = getcpy(VRSN_FIELD);
217 vp = concat(" ", VRSN_VALUE, "\n", NULL);
218 add_header(ct, np, vp);
221 ** We initally assume we will find multiple contents in the
222 ** draft. So create a multipart/mixed content to hold everything.
223 ** We can remove this later, if it is not needed.
225 if (get_ctinfo("multipart/mixed", ct, 0) == NOTOK)
227 ct->c_type = CT_MULTIPART;
228 ct->c_subtype = MULTI_MIXED;
229 ct->c_file = getcpy(infile);
231 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
232 adios(NULL, "out of memory");
233 ct->c_ctparams = (void *) m;
237 ** read and parse the composition file
238 ** and the directives it contains.
240 while (fgetstr(buf, sizeof(buf) - 1, in)) {
244 if (user_content(in, infile, buf, &p) == DONE) {
245 admonish(NULL, "ignoring spurious #end");
251 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
252 adios(NULL, "out of memory");
259 ** close the composition draft since
260 ** it's not needed any longer.
264 /* check if any contents were found */
266 adios(NULL, "no content directives found");
269 ** If only one content was found, then remove and
270 ** free the outer multipart content.
272 if (!m->mp_parts->mp_next) {
275 p = m->mp_parts->mp_part;
276 m->mp_parts->mp_part = NULL;
278 /* move header fields */
279 p->c_first_hf = ct->c_first_hf;
280 p->c_last_hf = ct->c_last_hf;
281 ct->c_first_hf = NULL;
282 ct->c_last_hf = NULL;
291 ** Fill out, or expand directives. Parse and execute
292 ** commands specified by profile composition strings.
296 if ((cp = strchr(prefix, 'a')) == NULL)
297 adios(NULL, "internal error(4)");
300 ** Scan the contents. Choose a transfer encoding, and
301 ** check if prefix for multipart boundary clashes with
302 ** any of the contents.
304 while (scan_content(ct) == NOTOK) {
309 adios(NULL, "giving up trying to find a unique delimiter string");
315 /* Build the rest of the header field structures */
323 ** Set up structures for placing unencoded
324 ** content when building parts.
328 init_decoded_content(CT ct)
332 if ((ce = (CE) calloc(1, sizeof(*ce))) == NULL)
333 adios(NULL, "out of memory");
336 ct->c_ceopenfnx = open7Bit; /* since unencoded */
337 ct->c_ceclosefnx = close_encoding;
338 ct->c_cesizefnx = NULL; /* since unencoded */
345 fgetstr(char *s, int n, FILE *stream)
349 for (ep = (cp = s) + n; cp < ep; ) {
352 if (!fgets(cp, n, stream))
353 return (cp != s ? s : NULL);
354 if (cp == s && *cp != '#')
357 cp += (i = strlen(cp)) - 1;
358 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
369 ** Parse the composition draft for text and directives.
370 ** Do initial setup of Content structure.
374 user_content(FILE *in, char *file, char *buf, CT *ctp)
383 struct str2init *s2i;
388 if (buf[0] == '\n' || strcmp(buf, "#\n") == 0) {
393 /* allocate basic Content structure */
394 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
395 adios(NULL, "out of memory");
398 /* allocate basic structure for handling decoded content */
399 init_decoded_content(ct);
406 ** Handle inline text. Check if line
407 ** is one of the following forms:
409 ** 1) doesn't begin with '#' (implicit directive)
410 ** 2) begins with "##" (implicit directive)
411 ** 3) begins with "#<"
413 if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
417 char content[BUFSIZ];
421 cp = m_mktemp2(NULL, invo_name, NULL, &out);
423 adios("mhbuildsbr", "unable to create temporary file");
425 /* use a temp file to collect the plain text lines */
426 ce->ce_file = getcpy(cp);
429 if (buf[0] == '#' && buf[1] == '<') {
430 strncpy(content, buf + 2, sizeof(content));
437 /* the directive is implicit */
438 strncpy(content, "text/plain", sizeof(content));
440 strncpy(buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
444 if (headers >= 0 && uprf(buffer, DESCR_FIELD) &&
445 buffer[i=strlen(DESCR_FIELD)] == ':') {
449 ct->c_descr = add(buffer + i + 1, ct->c_descr);
450 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
451 adios(NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
459 adios(NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
467 if (headers >= 0 && uprf(buffer, DISPO_FIELD)
468 && buffer[i = strlen(DISPO_FIELD)] == ':') {
472 ct->c_dispo = add(buffer + i + 1, ct->c_dispo);
473 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
474 adios(NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
482 adios(NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
490 if (headers != 1 || buffer[0] != '\n')
496 if ((cp = fgetstr(buffer, sizeof(buffer) - 1, in))
499 if (buffer[0] == '#') {
502 if (buffer[1] != '#')
504 for (cp = (bp = buffer) + 1; *cp; cp++)
511 ct->c_end = ftell(out);
514 /* parse content type */
515 if (get_ctinfo(content, ct, inlineD) == NOTOK)
518 for (s2i = str2cts; s2i->si_key; s2i++)
519 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
521 if (!s2i->si_key && !uprf(ci->ci_type, "X-"))
525 ** check type specified (possibly implicitly)
527 switch (ct->c_type = s2i->si_val) {
529 if (!mh_strcasecmp(ci->ci_subtype, "rfc822")) {
530 ct->c_encoding = CE_7BIT;
535 adios(NULL, "it doesn't make sense to define an in-line %s content",
536 ct->c_type == CT_MESSAGE ? "message" :
542 if ((ct->c_ctinitfnx = s2i->si_init))
543 (*ct->c_ctinitfnx) (ct);
548 fseek(in, pos, SEEK_SET);
553 ** If we've reached this point, the next line
554 ** must be some type of explicit directive.
557 /* check if directive is external-type */
558 extrnal = (buf[1] == '@');
560 /* parse directive */
561 if (get_ctinfo(buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
564 /* check directive against the list of MIME types */
565 for (s2i = str2cts; s2i->si_key; s2i++)
566 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
570 ** Check if the directive specified a valid type.
571 ** This will happen if it was one of the following forms:
573 ** #type/subtype (or)
578 adios(NULL, "missing subtype in \"#%s\"", ci->ci_type);
580 switch (ct->c_type = s2i->si_val) {
582 adios(NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
586 if (!mh_strcasecmp(ci->ci_subtype, "partial"))
587 adios(NULL, "sorry, \"#%s/%s\" isn't supported", ci->ci_type, ci->ci_subtype);
588 if (!mh_strcasecmp(ci->ci_subtype, "external-body"))
589 adios(NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
591 adios(NULL, "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
595 if ((ct->c_ctinitfnx = s2i->si_init))
596 (*ct->c_ctinitfnx) (ct);
601 ** #@type/subtype (external types directive)
608 adios(NULL, "need external information for \"#@%s/%s\"", 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
619 ** structure for the message/external-body to wrap
622 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
623 adios(NULL, "out of memory");
626 if (get_ctinfo(buffer, ct, 0) == NOTOK)
628 ct->c_type = CT_MESSAGE;
629 ct->c_subtype = MESSAGE_EXTERNAL;
631 if ((e = (struct exbody *)
632 calloc(1, sizeof(*e))) == NULL)
633 adios(NULL, "out of memory");
634 ct->c_ctparams = (void *) e;
640 if (params_external(ct, 1) == NOTOK)
646 /* Handle [file] argument */
648 /* check if specifies command to execute */
649 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
650 for (cp = ci->ci_magic + 1; isspace(*cp); cp++)
653 adios(NULL, "empty pipe command for #%s directive", ci->ci_type);
658 /* record filename of decoded contents */
659 ce->ce_file = ci->ci_magic;
660 if (access(ce->ce_file, R_OK) == NOTOK)
661 adios("reading", "unable to access %s for", ce->ce_file);
662 if (listsw && stat(ce->ce_file, &st) != NOTOK)
663 ct->c_end = (long) st.st_size;
670 ** No [file] argument, so check profile for
671 ** method to compose content.
673 snprintf(buffer, sizeof(buffer), "%s-compose-%s/%s",
674 invo_name, ci->ci_type, ci->ci_subtype);
675 if ((cp = context_find(buffer)) == NULL || *cp == '\0') {
676 snprintf(buffer, sizeof(buffer), "%s-compose-%s",
677 invo_name, ci->ci_type);
678 if ((cp = context_find(buffer)) == NULL ||
680 content_error(NULL, ct, "don't know how to compose content");
684 ci->ci_magic = getcpy(cp);
689 adios(NULL, "external definition not allowed for \"#%s\"",
694 ** #forw [+folder] [msgs]
696 if (!mh_strcasecmp(ci->ci_type, "forw")) {
698 char *folder, *arguments[MAXARGS];
702 ap = brkstring(ci->ci_magic, " ", "\n");
703 copyip(ap, arguments, MAXARGS);
705 arguments[0] = seq_cur;
710 /* search the arguments for a folder name */
711 for (ap = arguments; *ap; ap++) {
713 if (*cp == '+' || *cp == '@') {
715 adios(NULL, "only one folder per #forw directive");
717 folder = getcpy(expandfol(cp));
721 /* else, use the current folder */
723 folder = getcpy(getcurfol());
725 if (!(mp = folder_read(folder)))
726 adios(NULL, "unable to read folder %s", folder);
727 for (ap = arguments; *ap; ap++) {
729 if (*cp != '+' && *cp != '@')
730 if (!m_convert(mp, cp))
737 ** If there is more than one message to include, make this
738 ** a content of type "multipart/digest" and insert each message
739 ** as a subpart. If there is only one message, then make this
740 ** a content of type "message/rfc822".
742 if (mp->numsel > 1) {
743 /* we are forwarding multiple messages */
744 if (get_ctinfo("multipart/digest", ct, 0) == NOTOK)
746 ct->c_type = CT_MULTIPART;
747 ct->c_subtype = MULTI_DIGEST;
749 if ((m = (struct multipart *)
750 calloc(1, sizeof(*m))) == NULL)
751 adios(NULL, "out of memory");
752 ct->c_ctparams = (void *) m;
755 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
756 if (is_selected(mp, msgnum)) {
761 if ((p = (CT) calloc(1, sizeof(*p)))
763 adios(NULL, "out of memory");
764 init_decoded_content(p);
766 if (get_ctinfo("message/rfc822", p, 0)
769 p->c_type = CT_MESSAGE;
770 p->c_subtype = MESSAGE_RFC822;
772 snprintf(buffer, sizeof(buffer),
773 "%s/%d", mp->foldpath,
775 pe->ce_file = getcpy(buffer);
776 if (listsw && stat(pe->ce_file, &st)
778 p->c_end = (long) st.st_size;
780 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
781 adios(NULL, "out of memory");
788 /* we are forwarding one message */
789 if (get_ctinfo("message/rfc822", ct, 0) == NOTOK)
791 ct->c_type = CT_MESSAGE;
792 ct->c_subtype = MESSAGE_RFC822;
795 snprintf(buffer, sizeof(buffer), "%s/%d",
796 mp->foldpath, msgnum);
797 ce->ce_file = getcpy(buffer);
798 if (listsw && stat(ce->ce_file, &st) != NOTOK)
799 ct->c_end = (long) st.st_size;
802 folder_free(mp); /* free folder/message structure */
809 if (!mh_strcasecmp(ci->ci_type, "end")) {
816 ** #begin [ alternative | parallel ]
818 if (!mh_strcasecmp(ci->ci_type, "begin")) {
821 cp = SubMultiPart[vrsn - 1].kv_key;
822 } else if (!mh_strcasecmp(ci->ci_magic, "alternative")) {
823 vrsn = MULTI_ALTERNATE;
824 cp = SubMultiPart[vrsn - 1].kv_key;
825 } else if (!mh_strcasecmp(ci->ci_magic, "parallel")) {
826 vrsn = MULTI_PARALLEL;
827 cp = SubMultiPart[vrsn - 1].kv_key;
828 } else if (uprf(ci->ci_magic, "digest")) {
831 vrsn = MULTI_UNKNOWN;
836 snprintf(buffer, sizeof(buffer), "multipart/%s", cp);
837 if (get_ctinfo(buffer, ct, 0) == NOTOK)
839 ct->c_type = CT_MULTIPART;
840 ct->c_subtype = vrsn;
842 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
843 adios(NULL, "out of memory");
844 ct->c_ctparams = (void *) m;
847 while (fgetstr(buffer, sizeof(buffer) - 1, in)) {
851 if (user_content(in, file, buffer, &p) == DONE) {
853 adios(NULL, "empty \"#begin ... #end\" sequence");
859 if ((part = (struct part *)
860 calloc(1, sizeof(*part))) == NULL)
861 adios(NULL, "out of memory");
866 admonish(NULL, "premature end-of-file, missing #end");
873 adios(NULL, "unknown directive \"#%s\"", ci->ci_type);
874 return NOTOK; /* NOT REACHED */
879 set_id(CT ct, int top)
883 static time_t clock = 0;
888 snprintf(msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
889 (int) getpid(), (long) clock, LocalName());
891 msgfmt = getcpy(msgid);
893 snprintf(msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
894 ct->c_id = getcpy(msgid);
898 static char ebcdicsafe[0x100] = {
899 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
900 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
901 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
903 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
904 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
905 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
906 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
907 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
908 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
909 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
910 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
911 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
912 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
913 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
914 0x01, 0x01, 0x01, 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,
918 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
919 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
920 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
921 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
922 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
923 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
924 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
925 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
926 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
927 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
928 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
929 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
930 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
935 ** Fill out, or expand the various contents in the composition
936 ** draft. Read-in any necessary files. Parse and execute any
937 ** commands specified by profile composition strings.
941 compose_content(CT ct)
943 CE ce = ct->c_cefile;
945 switch (ct->c_type) {
950 char partnam[BUFSIZ];
951 struct multipart *m = (struct multipart *) ct->c_ctparams;
955 snprintf(partnam, sizeof(partnam), "%s.",
957 pp = partnam + strlen(partnam);
962 /* first, we call compose_content on all the subparts */
963 for (part = m->mp_parts, partnum = 1; part;
964 part = part->mp_next, partnum++) {
965 CT p = part->mp_part;
967 sprintf(pp, "%d", partnum);
968 p->c_partno = getcpy(partnam);
969 if (compose_content(p) == NOTOK)
974 ** If the -rfc934mode switch is given, then check all
975 ** the subparts of a multipart/digest. If they are all
976 ** message/rfc822, then mark this content and all
977 ** subparts with the rfc934 compatibility mode flag.
979 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
982 for (part = m->mp_parts; part; part = part->mp_next) {
983 CT p = part->mp_part;
985 if (p->c_subtype != MESSAGE_RFC822) {
990 ct->c_rfc934 = is934;
991 for (part = m->mp_parts; part; part = part->mp_next) {
992 CT p = part->mp_part;
994 if ((p->c_rfc934 = is934))
1000 ct->c_end = (partnum = strlen(prefix) + 2) + 2;
1004 for (part = m->mp_parts; part; part = part->mp_next)
1005 ct->c_end += part->mp_part->c_end + partnum;
1011 /* Nothing to do for type message */
1015 ** Discrete types (text/application/audio/image/video)
1020 int i, xstdout, len, buflen;
1021 char *bp, **ap, *cp;
1022 char *vec[4], buffer[BUFSIZ];
1024 CI ci = &ct->c_ctinfo;
1027 if (!(cp = ci->ci_magic))
1028 adios(NULL, "internal error(5)");
1030 tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1031 if (tfile == NULL) {
1032 adios("mhbuildsbr", "unable to create temporary file");
1034 ce->ce_file = getcpy(tfile);
1039 /* Get buffer ready to go */
1042 buflen = sizeof(buffer);
1045 ** Parse composition string into buffer
1047 for ( ; *cp; cp++) {
1053 ** insert parameters from
1059 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1060 snprintf(bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1070 /* %f, and stdout is not-redirected */
1076 ** insert temporary filename
1077 ** where content should be
1080 snprintf(bp, buflen, "%s", ce->ce_file);
1084 /* insert content subtype */
1085 strncpy(bp, ci->ci_subtype, buflen);
1089 /* insert character % */
1110 printf("composing content %s/%s from command\n\t%s\n", ci->ci_type, ci->ci_subtype, buffer);
1112 fflush(stdout); /* not sure if need for -noverbose */
1119 if ((out = fopen(ce->ce_file, "w")) == NULL)
1120 adios(ce->ce_file, "unable to open for writing");
1122 for (i = 0; (child_id = fork()) == NOTOK && i > 5; i++)
1126 adios("fork", "unable to fork");
1131 dup2(fileno(out), 1);
1133 execvp("/bin/sh", vec);
1134 fprintf(stderr, "unable to exec ");
1141 if (pidXwait(child_id, NULL))
1147 /* Check size of file */
1148 if (listsw && ct->c_end == 0L) {
1151 if (stat(ce->ce_file, &st) != NOTOK)
1152 ct->c_end = (long) st.st_size;
1162 ** Scan the content.
1164 ** 1) choose a transfer encoding.
1165 ** 2) check for clashes with multipart boundary string.
1166 ** 3) for text content, figure out which character set is being used.
1168 ** If there is a clash with one of the contents and the multipart boundary,
1169 ** this function will exit with NOTOK. This will cause the scanning process
1170 ** to be repeated with a different multipart boundary. It is possible
1171 ** (although highly unlikely) that this scan will be repeated multiple times.
1178 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1179 int checklinelen = 0, linelen = 0; /* check for long lines */
1180 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1181 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1182 int checkebcdic = 0, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
1183 unsigned char *cp = NULL, buffer[BUFSIZ];
1184 struct text *t = NULL;
1186 CE ce = ct->c_cefile;
1189 ** handle multipart by scanning all subparts
1190 ** and then checking their encoding.
1192 if (ct->c_type == CT_MULTIPART) {
1193 struct multipart *m = (struct multipart *) ct->c_ctparams;
1196 /* initially mark the domain of enclosing multipart as 7bit */
1197 ct->c_encoding = CE_7BIT;
1199 for (part = m->mp_parts; part; part = part->mp_next) {
1200 CT p = part->mp_part;
1202 if (scan_content(p) == NOTOK) {
1203 /* choose encoding for subpart */
1208 ** if necessary, enlarge encoding for enclosing
1211 if (p->c_encoding == CE_BINARY)
1212 ct->c_encoding = CE_BINARY;
1213 if (p->c_encoding == CE_8BIT &&
1214 ct->c_encoding != CE_BINARY)
1215 ct->c_encoding = CE_8BIT;
1222 ** Decide what to check while scanning this content.
1224 switch (ct->c_type) {
1228 if (ct->c_subtype == TEXT_PLAIN) {
1233 checkebcdic = ebcdicsw;
1239 case CT_APPLICATION:
1241 checkebcdic = ebcdicsw;
1253 /* don't check anything for message/external */
1254 if (ct->c_subtype == MESSAGE_EXTERNAL)
1264 ** Don't check anything for these types,
1265 ** since we are forcing use of base64.
1276 ** Scan the unencoded content
1278 if (check8bit || checklinelen || checklinespace || checkboundary) {
1279 if ((in = fopen(ce->ce_file, "r")) == NULL)
1280 adios(ce->ce_file, "unable to open for reading");
1281 len = strlen(prefix);
1283 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1285 ** Check for 8bit data.
1288 for (cp = buffer; *cp; cp++) {
1289 if (!isascii(*cp)) {
1291 /* no need to keep checking */
1295 ** Check if character is ebcdic-safe.
1296 ** We only check this if also checking
1299 if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1301 /* no need to keep checking */
1308 ** Check line length.
1310 if (checklinelen && (strlen(buffer) > CPERLIN + 1)) {
1312 checklinelen = 0; /* no need to keep checking */
1316 ** Check if line ends with a space.
1318 if (checklinespace &&
1319 (cp = buffer + strlen(buffer) - 2) >
1320 buffer && isspace(*cp)) {
1322 /* no need to keep checking */
1327 ** Check if content contains a line that clashes
1328 ** with our standard boundary for multipart messages.
1330 if (checkboundary && buffer[0] == '-' &&
1332 for (cp = buffer + strlen(buffer) - 1;
1337 if (strncmp(buffer + 2, prefix, len)==0 &&
1338 isdigit(buffer[2 + len])) {
1340 /* no need to keep checking */
1349 ** Decide which transfer encoding to use.
1351 switch (ct->c_type) {
1354 ** If the text content didn't specify a character
1355 ** set, we need to figure out which one was used.
1357 t = (struct text *) ct->c_ctparams;
1358 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1359 CI ci = &ct->c_ctinfo;
1362 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1366 t->tx_charset = CHARSET_UNKNOWN;
1367 *ap = concat("charset=", write_charset_8bit(),
1370 t->tx_charset = CHARSET_USASCII;
1371 *ap = getcpy("charset=us-ascii");
1374 cp = strchr(*ap++, '=');
1380 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1382 ct->c_encoding = CE_QUOTED;
1384 ct->c_encoding = CE_7BIT;
1387 case CT_APPLICATION:
1388 /* For application type, use base64, except when postscript */
1389 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1391 ct->c_encoding = (ct->c_subtype ==
1392 APPLICATION_POSTSCRIPT) ?
1393 CE_QUOTED : CE_BASE64;
1395 ct->c_encoding = CE_7BIT;
1399 ct->c_encoding = CE_7BIT;
1405 /* For audio, image, and video contents, just use base64 */
1406 ct->c_encoding = CE_BASE64;
1410 return (boundaryclash ? NOTOK : OK);
1415 ** Scan the content structures, and build header
1416 ** fields that will need to be output into the
1421 build_headers(CT ct)
1423 int cc, mailbody, len;
1425 char *np, *vp, buffer[BUFSIZ];
1426 CI ci = &ct->c_ctinfo;
1429 ** If message is type multipart, then add the multipart
1430 ** boundary to the list of attribute/value pairs.
1432 if (ct->c_type == CT_MULTIPART) {
1434 static int level = 0; /* store nesting level */
1438 snprintf(buffer, sizeof(buffer), "boundary=%s%d",
1440 cp = strchr(*ap++ = getcpy(buffer), '=');
1447 ** Skip the output of Content-Type, parameters, content
1448 ** description and disposition, and Content-ID if the
1449 ** content is of type "message" and the rfc934 compatibility
1450 ** flag is set (which means we are inside multipart/digest
1451 ** and the switch -rfc934mode was given).
1453 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1457 ** output the content type and subtype
1459 np = getcpy(TYPE_FIELD);
1460 vp = concat(" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1462 /* keep track of length of line */
1463 len = strlen(TYPE_FIELD) + strlen(ci->ci_type) +
1464 strlen(ci->ci_subtype) + 3;
1466 mailbody = ct->c_type == CT_MESSAGE &&
1467 ct->c_subtype == MESSAGE_EXTERNAL &&
1468 ((struct exbody *) ct->c_ctparams)->eb_body;
1471 ** Append the attribute/value pairs to
1472 ** the end of the Content-Type line.
1474 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1475 if (mailbody && !mh_strcasecmp(*ap, "body"))
1481 snprintf(buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1482 if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) {
1483 vp = add("\n\t", vp);
1489 vp = add(buffer, vp);
1494 ** Append any RFC-822 comment to the end of
1495 ** the Content-Type line.
1497 if (ci->ci_comment) {
1498 snprintf(buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1499 if (len + 1 + (cc = 2 + strlen(ci->ci_comment)) >= CPERLIN) {
1500 vp = add("\n\t", vp);
1506 vp = add(buffer, vp);
1510 add_header(ct, np, vp);
1513 ** output the Content-ID, unless disabled by -nocontentid
1515 if (contentidsw && ct->c_id) {
1516 np = getcpy(ID_FIELD);
1517 vp = concat(" ", ct->c_id, NULL);
1518 add_header(ct, np, vp);
1522 ** output the Content-Description
1525 np = getcpy(DESCR_FIELD);
1526 vp = concat(" ", ct->c_descr, NULL);
1527 add_header(ct, np, vp);
1531 ** output the Content-Disposition
1534 np = getcpy(DISPO_FIELD);
1535 vp = concat(" ", ct->c_dispo, NULL);
1536 add_header(ct, np, vp);
1541 ** If this is the internal content structure for a
1542 ** "message/external", then we are done with the
1543 ** headers (since it has no body).
1549 ** output the Content-MD5
1552 np = getcpy(MD5_FIELD);
1553 vp = calculate_digest(ct, (ct->c_encoding == CE_QUOTED) ?
1555 add_header(ct, np, vp);
1559 ** output the Content-Transfer-Encoding
1561 switch (ct->c_encoding) {
1563 /* Nothing to output */
1567 if (ct->c_type == CT_MESSAGE)
1568 adios(NULL, "internal error, invalid encoding");
1570 np = getcpy(ENCODING_FIELD);
1571 vp = concat(" ", "8bit", "\n", NULL);
1572 add_header(ct, np, vp);
1576 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1577 adios(NULL, "internal error, invalid encoding");
1579 np = getcpy(ENCODING_FIELD);
1580 vp = concat(" ", "quoted-printable", "\n", NULL);
1581 add_header(ct, np, vp);
1585 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1586 adios(NULL, "internal error, invalid encoding");
1588 np = getcpy(ENCODING_FIELD);
1589 vp = concat(" ", "base64", "\n", NULL);
1590 add_header(ct, np, vp);
1594 if (ct->c_type == CT_MESSAGE)
1595 adios(NULL, "internal error, invalid encoding");
1597 np = getcpy(ENCODING_FIELD);
1598 vp = concat(" ", "binary", "\n", NULL);
1599 add_header(ct, np, vp);
1603 adios(NULL, "unknown transfer encoding in content");
1608 ** Additional content specific header processing
1610 switch (ct->c_type) {
1613 struct multipart *m;
1616 m = (struct multipart *) ct->c_ctparams;
1617 for (part = m->mp_parts; part; part = part->mp_next) {
1627 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1630 e = (struct exbody *) ct->c_ctparams;
1631 build_headers(e->eb_content);
1644 static char nib2b64[0x40+1] =
1645 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1648 calculate_digest(CT ct, int asciiP)
1651 char buffer[BUFSIZ], *vp, *op;
1653 unsigned char digest[16];
1654 unsigned char outbuf[25];
1657 CE ce = ct->c_cefile;
1660 if ((in = fopen(ce->ce_file, "r")) == NULL)
1661 adios(ce->ce_file, "unable to open for reading");
1663 /* Initialize md5 context */
1664 MD5Init(&mdContext);
1666 /* calculate md5 message digest */
1668 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1671 cp = buffer + strlen(buffer) - 1;
1672 if ((c = *cp) == '\n')
1675 MD5Update(&mdContext, (unsigned char *) buffer,
1676 (unsigned int) strlen(buffer));
1679 MD5Update(&mdContext, (unsigned char *) "\r\n",
1683 while ((cc = fread(buffer, sizeof(*buffer), sizeof(buffer),
1685 MD5Update(&mdContext, (unsigned char *) buffer,
1689 /* md5 finalization. Write digest and zero md5 context */
1690 MD5Final(digest, &mdContext);
1695 /* print debugging info */
1699 fprintf(stderr, "MD5 digest=");
1700 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1702 fprintf(stderr, "%02x", *dp & 0xff);
1703 fprintf(stderr, "\n");
1706 /* encode the digest using base64 */
1707 for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
1708 cc > 0; cc -= 3, op += 4) {
1712 bits = (*dp++ & 0xff) << 16;
1714 bits |= (*dp++ & 0xff) << 8;
1716 bits |= *dp++ & 0xff;
1719 for (bp = op + 4; bp > op; bits >>= 6)
1720 *--bp = nib2b64[bits & 0x3f];
1728 /* null terminate string */
1731 /* now make copy and return string */
1732 vp = concat(" ", outbuf, "\n", NULL);