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>
26 #include <h/mhparse.h>
29 #ifdef TIME_WITH_SYS_TIME
30 # include <sys/time.h>
33 # ifdef TM_IN_SYS_TIME
34 # include <sys/time.h>
40 #ifdef HAVE_SYS_WAIT_H
41 # include <sys/wait.h>
51 extern int contentidsw;
53 extern int endian; /* mhmisc.c */
56 extern int rcachesw; /* mhcachesbr.c */
57 extern int wcachesw; /* mhcachesbr.c */
60 ** Directory to place tmp files. This must
61 ** be set before these routines are called.
67 static char prefix[] = "----- =_aaaaaaaaaa";
71 int make_intermediates(char *);
72 void content_error(char *, CT, char *, ...);
75 int find_cache(CT, int, int *, char *, char *, int);
78 int ftp_get(char *, char *, char *, char *, char *, char *, int, int);
81 void free_content(CT);
83 void free_encoding(CT, int);
88 CT build_mime(char *);
93 static int init_decoded_content(CT);
94 static char *fgetstr(char *, int, FILE *);
95 static int user_content(FILE *, char *, char *, CT *);
96 static void set_id(CT, int);
97 static int compose_content(CT);
98 static int scan_content(CT);
99 static int build_headers(CT);
100 static char *calculate_digest(CT, int);
104 ** Main routine for translating composition file
105 ** into valid MIME message. It translates the draft
106 ** into a content structure (actually a tree of content
107 ** structures). This message then can be manipulated
108 ** in various ways, including being output via
113 build_mime(char *infile)
116 char buf[BUFSIZ], name[NAMESZ];
125 /* open the composition draft */
126 if ((in = fopen(infile, "r")) == NULL)
127 adios(infile, "unable to open for reading");
130 ** Allocate space for primary (outside) content
132 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
133 adios(NULL, "out of memory");
136 ** Allocate structure for handling decoded content
137 ** for this part. We don't really need this, but
138 ** allocate it to remain consistent.
140 init_decoded_content(ct);
143 ** Parse some of the header fields in the composition
144 ** draft into the linked list of header fields for
145 ** the new MIME message.
147 for (compnum = 1, state = FLD;;) {
148 switch (state = m_getfld(state, name, buf, sizeof(buf), in)) {
154 /* abort if draft has Mime-Version header field */
155 if (!mh_strcasecmp(name, VRSN_FIELD))
156 adios(NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
159 ** abort if draft has Content-Transfer-Encoding
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,
173 /* get copies of the buffers */
177 /* if necessary, get rest of field */
178 while (state == FLDPLUS) {
179 state = m_getfld(state, name, buf,
181 vp = add(buf, vp); /* add to prev value */
184 /* Now add the header data to the list */
185 add_header(ct, np, vp);
188 /* if this wasn't the last hdr field, then continue */
194 adios(NULL, "draft has empty body -- no directives!");
199 fseek(in, (long) (-strlen(buf)), SEEK_CUR);
204 adios(NULL, "message format error in component #%d",
208 adios(NULL, "getfld() returned %d", state);
214 ** Now add the MIME-Version header field
215 ** to the list of header fields.
217 np = getcpy(VRSN_FIELD);
218 vp = concat(" ", VRSN_VALUE, "\n", NULL);
219 add_header(ct, np, vp);
222 ** We initally assume we will find multiple contents in the
223 ** draft. So create a multipart/mixed content to hold everything.
224 ** We can remove this later, if it is not needed.
226 if (get_ctinfo("multipart/mixed", ct, 0) == NOTOK)
228 ct->c_type = CT_MULTIPART;
229 ct->c_subtype = MULTI_MIXED;
230 ct->c_file = getcpy(infile);
232 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
233 adios(NULL, "out of memory");
234 ct->c_ctparams = (void *) m;
238 ** read and parse the composition file
239 ** and the directives it contains.
241 while (fgetstr(buf, sizeof(buf) - 1, in)) {
245 if (user_content(in, infile, buf, &p) == DONE) {
246 admonish(NULL, "ignoring spurious #end");
252 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
253 adios(NULL, "out of memory");
260 ** close the composition draft since
261 ** it's not needed any longer.
265 /* check if any contents were found */
267 adios(NULL, "no content directives found");
270 ** If only one content was found, then remove and
271 ** free the outer multipart content.
273 if (!m->mp_parts->mp_next) {
276 p = m->mp_parts->mp_part;
277 m->mp_parts->mp_part = NULL;
279 /* move header fields */
280 p->c_first_hf = ct->c_first_hf;
281 p->c_last_hf = ct->c_last_hf;
282 ct->c_first_hf = NULL;
283 ct->c_last_hf = NULL;
292 ** Fill out, or expand directives. Parse and execute
293 ** commands specified by profile composition strings.
297 if ((cp = strchr(prefix, 'a')) == NULL)
298 adios(NULL, "internal error(4)");
301 ** Scan the contents. Choose a transfer encoding, and
302 ** check if prefix for multipart boundary clashes with
303 ** any of the contents.
305 while (scan_content(ct) == NOTOK) {
310 adios(NULL, "giving up trying to find a unique delimiter string");
316 /* Build the rest of the header field structures */
324 ** Set up structures for placing unencoded
325 ** content when building parts.
329 init_decoded_content(CT ct)
333 if ((ce = (CE) calloc(1, sizeof(*ce))) == NULL)
334 adios(NULL, "out of memory");
337 ct->c_ceopenfnx = open7Bit; /* since unencoded */
338 ct->c_ceclosefnx = close_encoding;
339 ct->c_cesizefnx = NULL; /* since unencoded */
346 fgetstr(char *s, int n, FILE *stream)
350 for (ep = (cp = s) + n; cp < ep; ) {
353 if (!fgets(cp, n, stream))
354 return (cp != s ? s : NULL);
355 if (cp == s && *cp != '#')
358 cp += (i = strlen(cp)) - 1;
359 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
370 ** Parse the composition draft for text and directives.
371 ** Do initial setup of Content structure.
375 user_content(FILE *in, char *file, char *buf, CT *ctp)
384 struct str2init *s2i;
389 if (buf[0] == '\n' || strcmp(buf, "#\n") == 0) {
394 /* allocate basic Content structure */
395 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
396 adios(NULL, "out of memory");
399 /* allocate basic structure for handling decoded content */
400 init_decoded_content(ct);
407 ** Handle inline text. Check if line
408 ** is one of the following forms:
410 ** 1) doesn't begin with '#' (implicit directive)
411 ** 2) begins with "##" (implicit directive)
412 ** 3) begins with "#<"
414 if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
418 char content[BUFSIZ];
422 cp = m_mktemp2(NULL, invo_name, NULL, &out);
424 adios("mhbuildsbr", "unable to create temporary file");
426 /* use a temp file to collect the plain text lines */
427 ce->ce_file = getcpy(cp);
430 if (buf[0] == '#' && buf[1] == '<') {
431 strncpy(content, buf + 2, sizeof(content));
438 /* the directive is implicit */
439 strncpy(content, "text/plain", sizeof(content));
441 strncpy(buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
445 if (headers >= 0 && uprf(buffer, DESCR_FIELD) &&
446 buffer[i=strlen(DESCR_FIELD)] == ':') {
450 ct->c_descr = add(buffer + i + 1, ct->c_descr);
451 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
452 adios(NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
460 adios(NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
468 if (headers >= 0 && uprf(buffer, DISPO_FIELD)
469 && buffer[i = strlen(DISPO_FIELD)] == ':') {
473 ct->c_dispo = add(buffer + i + 1, ct->c_dispo);
474 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
475 adios(NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
483 adios(NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
491 if (headers != 1 || buffer[0] != '\n')
497 if ((cp = fgetstr(buffer, sizeof(buffer) - 1, in))
500 if (buffer[0] == '#') {
503 if (buffer[1] != '#')
505 for (cp = (bp = buffer) + 1; *cp; cp++)
512 ct->c_end = ftell(out);
515 /* parse content type */
516 if (get_ctinfo(content, ct, inlineD) == NOTOK)
519 for (s2i = str2cts; s2i->si_key; s2i++)
520 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
522 if (!s2i->si_key && !uprf(ci->ci_type, "X-"))
526 ** check type specified (possibly implicitly)
528 switch (ct->c_type = s2i->si_val) {
530 if (!mh_strcasecmp(ci->ci_subtype, "rfc822")) {
531 ct->c_encoding = CE_7BIT;
536 adios(NULL, "it doesn't make sense to define an in-line %s content",
537 ct->c_type == CT_MESSAGE ? "message" :
543 if ((ct->c_ctinitfnx = s2i->si_init))
544 (*ct->c_ctinitfnx) (ct);
549 fseek(in, pos, SEEK_SET);
554 ** If we've reached this point, the next line
555 ** must be some type of explicit directive.
558 /* check if directive is external-type */
559 extrnal = (buf[1] == '@');
561 /* parse directive */
562 if (get_ctinfo(buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
565 /* check directive against the list of MIME types */
566 for (s2i = str2cts; s2i->si_key; s2i++)
567 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
571 ** Check if the directive specified a valid type.
572 ** This will happen if it was one of the following forms:
574 ** #type/subtype (or)
579 adios(NULL, "missing subtype in \"#%s\"", ci->ci_type);
581 switch (ct->c_type = s2i->si_val) {
583 adios(NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
587 if (!mh_strcasecmp(ci->ci_subtype, "partial"))
588 adios(NULL, "sorry, \"#%s/%s\" isn't supported", ci->ci_type, ci->ci_subtype);
589 if (!mh_strcasecmp(ci->ci_subtype, "external-body"))
590 adios(NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
592 adios(NULL, "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
596 if ((ct->c_ctinitfnx = s2i->si_init))
597 (*ct->c_ctinitfnx) (ct);
602 ** #@type/subtype (external types directive)
609 adios(NULL, "need external information for \"#@%s/%s\"", ci->ci_type, ci->ci_subtype);
612 snprintf(buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
617 ** Since we are using the current Content structure to
618 ** hold information about the type of the external
619 ** reference, we need to create another Content
620 ** structure for the message/external-body to wrap
623 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
624 adios(NULL, "out of memory");
627 if (get_ctinfo(buffer, ct, 0) == NOTOK)
629 ct->c_type = CT_MESSAGE;
630 ct->c_subtype = MESSAGE_EXTERNAL;
632 if ((e = (struct exbody *)
633 calloc(1, sizeof(*e))) == NULL)
634 adios(NULL, "out of memory");
635 ct->c_ctparams = (void *) e;
641 if (params_external(ct, 1) == NOTOK)
647 /* Handle [file] argument */
649 /* check if specifies command to execute */
650 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
651 for (cp = ci->ci_magic + 1; isspace(*cp); cp++)
654 adios(NULL, "empty pipe command for #%s directive", ci->ci_type);
659 /* record filename of decoded contents */
660 ce->ce_file = ci->ci_magic;
661 if (access(ce->ce_file, R_OK) == NOTOK)
662 adios("reading", "unable to access %s for", ce->ce_file);
663 if (listsw && stat(ce->ce_file, &st) != NOTOK)
664 ct->c_end = (long) st.st_size;
671 ** No [file] argument, so check profile for
672 ** method to compose content.
674 snprintf(buffer, sizeof(buffer), "%s-compose-%s/%s",
675 invo_name, ci->ci_type, ci->ci_subtype);
676 if ((cp = context_find(buffer)) == NULL || *cp == '\0') {
677 snprintf(buffer, sizeof(buffer), "%s-compose-%s",
678 invo_name, ci->ci_type);
679 if ((cp = context_find(buffer)) == NULL ||
681 content_error(NULL, ct, "don't know how to compose content");
685 ci->ci_magic = getcpy(cp);
690 adios(NULL, "external definition not allowed for \"#%s\"",
695 ** #forw [+folder] [msgs]
697 if (!mh_strcasecmp(ci->ci_type, "forw")) {
699 char *folder, *arguments[MAXARGS];
703 ap = brkstring(ci->ci_magic, " ", "\n");
704 copyip(ap, arguments, MAXARGS);
706 arguments[0] = seq_cur;
711 /* search the arguments for a folder name */
712 for (ap = arguments; *ap; ap++) {
714 if (*cp == '+' || *cp == '@') {
716 adios(NULL, "only one folder per #forw directive");
718 folder = getcpy(expandfol(cp));
722 /* else, use the current folder */
724 folder = getcpy(getcurfol());
726 if (!(mp = folder_read(folder)))
727 adios(NULL, "unable to read folder %s", folder);
728 for (ap = arguments; *ap; ap++) {
730 if (*cp != '+' && *cp != '@')
731 if (!m_convert(mp, cp))
738 ** If there is more than one message to include, make this
739 ** a content of type "multipart/digest" and insert each message
740 ** as a subpart. If there is only one message, then make this
741 ** a content of type "message/rfc822".
743 if (mp->numsel > 1) {
744 /* we are forwarding multiple messages */
745 if (get_ctinfo("multipart/digest", ct, 0) == NOTOK)
747 ct->c_type = CT_MULTIPART;
748 ct->c_subtype = MULTI_DIGEST;
750 if ((m = (struct multipart *)
751 calloc(1, sizeof(*m))) == NULL)
752 adios(NULL, "out of memory");
753 ct->c_ctparams = (void *) m;
756 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
757 if (is_selected(mp, msgnum)) {
762 if ((p = (CT) calloc(1, sizeof(*p)))
764 adios(NULL, "out of memory");
765 init_decoded_content(p);
767 if (get_ctinfo("message/rfc822", p, 0)
770 p->c_type = CT_MESSAGE;
771 p->c_subtype = MESSAGE_RFC822;
773 snprintf(buffer, sizeof(buffer),
774 "%s/%d", mp->foldpath,
776 pe->ce_file = getcpy(buffer);
777 if (listsw && stat(pe->ce_file, &st)
779 p->c_end = (long) st.st_size;
781 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
782 adios(NULL, "out of memory");
789 /* we are forwarding one message */
790 if (get_ctinfo("message/rfc822", ct, 0) == NOTOK)
792 ct->c_type = CT_MESSAGE;
793 ct->c_subtype = MESSAGE_RFC822;
796 snprintf(buffer, sizeof(buffer), "%s/%d",
797 mp->foldpath, msgnum);
798 ce->ce_file = getcpy(buffer);
799 if (listsw && stat(ce->ce_file, &st) != NOTOK)
800 ct->c_end = (long) st.st_size;
803 folder_free(mp); /* free folder/message structure */
810 if (!mh_strcasecmp(ci->ci_type, "end")) {
817 ** #begin [ alternative | parallel ]
819 if (!mh_strcasecmp(ci->ci_type, "begin")) {
822 cp = SubMultiPart[vrsn - 1].kv_key;
823 } else if (!mh_strcasecmp(ci->ci_magic, "alternative")) {
824 vrsn = MULTI_ALTERNATE;
825 cp = SubMultiPart[vrsn - 1].kv_key;
826 } else if (!mh_strcasecmp(ci->ci_magic, "parallel")) {
827 vrsn = MULTI_PARALLEL;
828 cp = SubMultiPart[vrsn - 1].kv_key;
829 } else if (uprf(ci->ci_magic, "digest")) {
832 vrsn = MULTI_UNKNOWN;
837 snprintf(buffer, sizeof(buffer), "multipart/%s", cp);
838 if (get_ctinfo(buffer, ct, 0) == NOTOK)
840 ct->c_type = CT_MULTIPART;
841 ct->c_subtype = vrsn;
843 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
844 adios(NULL, "out of memory");
845 ct->c_ctparams = (void *) m;
848 while (fgetstr(buffer, sizeof(buffer) - 1, in)) {
852 if (user_content(in, file, buffer, &p) == DONE) {
854 adios(NULL, "empty \"#begin ... #end\" sequence");
860 if ((part = (struct part *)
861 calloc(1, sizeof(*part))) == NULL)
862 adios(NULL, "out of memory");
867 admonish(NULL, "premature end-of-file, missing #end");
874 adios(NULL, "unknown directive \"#%s\"", ci->ci_type);
875 return NOTOK; /* NOT REACHED */
880 set_id(CT ct, int top)
884 static time_t clock = 0;
889 snprintf(msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
890 (int) getpid(), (long) clock, LocalName());
892 msgfmt = getcpy(msgid);
894 snprintf(msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
895 ct->c_id = getcpy(msgid);
899 static char ebcdicsafe[0x100] = {
900 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
901 0x00, 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 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
905 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
906 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
907 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
908 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
909 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
910 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
911 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
912 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
913 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
914 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
915 0x01, 0x01, 0x01, 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,
931 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
936 ** Fill out, or expand the various contents in the composition
937 ** draft. Read-in any necessary files. Parse and execute any
938 ** commands specified by profile composition strings.
942 compose_content(CT ct)
944 CE ce = ct->c_cefile;
946 switch (ct->c_type) {
951 char partnam[BUFSIZ];
952 struct multipart *m = (struct multipart *) ct->c_ctparams;
956 snprintf(partnam, sizeof(partnam), "%s.",
958 pp = partnam + strlen(partnam);
963 /* first, we call compose_content on all the subparts */
964 for (part = m->mp_parts, partnum = 1; part;
965 part = part->mp_next, partnum++) {
966 CT p = part->mp_part;
968 sprintf(pp, "%d", partnum);
969 p->c_partno = getcpy(partnam);
970 if (compose_content(p) == NOTOK)
975 ** If the -rfc934mode switch is given, then check all
976 ** the subparts of a multipart/digest. If they are all
977 ** message/rfc822, then mark this content and all
978 ** subparts with the rfc934 compatibility mode flag.
980 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
983 for (part = m->mp_parts; part; part = part->mp_next) {
984 CT p = part->mp_part;
986 if (p->c_subtype != MESSAGE_RFC822) {
991 ct->c_rfc934 = is934;
992 for (part = m->mp_parts; part; part = part->mp_next) {
993 CT p = part->mp_part;
995 if ((p->c_rfc934 = is934))
1001 ct->c_end = (partnum = strlen(prefix) + 2) + 2;
1005 for (part = m->mp_parts; part; part = part->mp_next)
1006 ct->c_end += part->mp_part->c_end + partnum;
1012 /* Nothing to do for type message */
1016 ** Discrete types (text/application/audio/image/video)
1021 int i, xstdout, len, buflen;
1022 char *bp, **ap, *cp;
1023 char *vec[4], buffer[BUFSIZ];
1025 CI ci = &ct->c_ctinfo;
1028 if (!(cp = ci->ci_magic))
1029 adios(NULL, "internal error(5)");
1031 tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1032 if (tfile == NULL) {
1033 adios("mhbuildsbr", "unable to create temporary file");
1035 ce->ce_file = getcpy(tfile);
1040 /* Get buffer ready to go */
1043 buflen = sizeof(buffer);
1046 ** Parse composition string into buffer
1048 for ( ; *cp; cp++) {
1054 ** insert parameters from
1060 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1061 snprintf(bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1071 /* %f, and stdout is not-redirected */
1077 ** insert temporary filename
1078 ** where content should be
1081 snprintf(bp, buflen, "%s", ce->ce_file);
1085 /* insert content subtype */
1086 strncpy(bp, ci->ci_subtype, buflen);
1090 /* insert character % */
1111 printf("composing content %s/%s from command\n\t%s\n", ci->ci_type, ci->ci_subtype, buffer);
1113 fflush(stdout); /* not sure if need for -noverbose */
1120 if ((out = fopen(ce->ce_file, "w")) == NULL)
1121 adios(ce->ce_file, "unable to open for writing");
1123 for (i = 0; (child_id = fork()) == NOTOK && i > 5; i++)
1127 adios("fork", "unable to fork");
1132 dup2(fileno(out), 1);
1134 execvp("/bin/sh", vec);
1135 fprintf(stderr, "unable to exec ");
1142 if (pidXwait(child_id, NULL))
1148 /* Check size of file */
1149 if (listsw && ct->c_end == 0L) {
1152 if (stat(ce->ce_file, &st) != NOTOK)
1153 ct->c_end = (long) st.st_size;
1163 ** Scan the content.
1165 ** 1) choose a transfer encoding.
1166 ** 2) check for clashes with multipart boundary string.
1167 ** 3) for text content, figure out which character set is being used.
1169 ** If there is a clash with one of the contents and the multipart boundary,
1170 ** this function will exit with NOTOK. This will cause the scanning process
1171 ** to be repeated with a different multipart boundary. It is possible
1172 ** (although highly unlikely) that this scan will be repeated multiple times.
1179 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1180 int checklinelen = 0, linelen = 0; /* check for long lines */
1181 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1182 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1183 int checkebcdic = 0, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
1184 unsigned char *cp = NULL, buffer[BUFSIZ];
1185 struct text *t = NULL;
1187 CE ce = ct->c_cefile;
1190 ** handle multipart by scanning all subparts
1191 ** and then checking their encoding.
1193 if (ct->c_type == CT_MULTIPART) {
1194 struct multipart *m = (struct multipart *) ct->c_ctparams;
1197 /* initially mark the domain of enclosing multipart as 7bit */
1198 ct->c_encoding = CE_7BIT;
1200 for (part = m->mp_parts; part; part = part->mp_next) {
1201 CT p = part->mp_part;
1203 if (scan_content(p) == NOTOK) {
1204 /* choose encoding for subpart */
1209 ** if necessary, enlarge encoding for enclosing
1212 if (p->c_encoding == CE_BINARY)
1213 ct->c_encoding = CE_BINARY;
1214 if (p->c_encoding == CE_8BIT &&
1215 ct->c_encoding != CE_BINARY)
1216 ct->c_encoding = CE_8BIT;
1223 ** Decide what to check while scanning this content.
1225 switch (ct->c_type) {
1229 if (ct->c_subtype == TEXT_PLAIN) {
1234 checkebcdic = ebcdicsw;
1240 case CT_APPLICATION:
1242 checkebcdic = ebcdicsw;
1254 /* don't check anything for message/external */
1255 if (ct->c_subtype == MESSAGE_EXTERNAL)
1265 ** Don't check anything for these types,
1266 ** since we are forcing use of base64.
1277 ** Scan the unencoded content
1279 if (check8bit || checklinelen || checklinespace || checkboundary) {
1280 if ((in = fopen(ce->ce_file, "r")) == NULL)
1281 adios(ce->ce_file, "unable to open for reading");
1282 len = strlen(prefix);
1284 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1286 ** Check for 8bit data.
1289 for (cp = buffer; *cp; cp++) {
1290 if (!isascii(*cp)) {
1292 /* no need to keep checking */
1296 ** Check if character is ebcdic-safe.
1297 ** We only check this if also checking
1300 if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1302 /* no need to keep checking */
1309 ** Check line length.
1311 if (checklinelen && (strlen(buffer) > CPERLIN + 1)) {
1313 checklinelen = 0; /* no need to keep checking */
1317 ** Check if line ends with a space.
1319 if (checklinespace &&
1320 (cp = buffer + strlen(buffer) - 2) >
1321 buffer && isspace(*cp)) {
1323 /* no need to keep checking */
1328 ** Check if content contains a line that clashes
1329 ** with our standard boundary for multipart messages.
1331 if (checkboundary && buffer[0] == '-' &&
1333 for (cp = buffer + strlen(buffer) - 1;
1338 if (!strncmp(buffer + 2, prefix, len) &&
1339 isdigit(buffer[2 + len])) {
1341 /* no need to keep checking */
1350 ** Decide which transfer encoding to use.
1352 switch (ct->c_type) {
1355 ** If the text content didn't specify a character
1356 ** set, we need to figure out which one was used.
1358 t = (struct text *) ct->c_ctparams;
1359 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1360 CI ci = &ct->c_ctinfo;
1363 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1367 t->tx_charset = CHARSET_UNKNOWN;
1368 *ap = concat("charset=", write_charset_8bit(),
1371 t->tx_charset = CHARSET_USASCII;
1372 *ap = getcpy("charset=us-ascii");
1375 cp = strchr(*ap++, '=');
1381 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1383 ct->c_encoding = CE_QUOTED;
1385 ct->c_encoding = CE_7BIT;
1388 case CT_APPLICATION:
1389 /* For application type, use base64, except when postscript */
1390 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1392 ct->c_encoding = (ct->c_subtype ==
1393 APPLICATION_POSTSCRIPT) ?
1394 CE_QUOTED : CE_BASE64;
1396 ct->c_encoding = CE_7BIT;
1400 ct->c_encoding = CE_7BIT;
1406 /* For audio, image, and video contents, just use base64 */
1407 ct->c_encoding = CE_BASE64;
1411 return (boundaryclash ? NOTOK : OK);
1416 ** Scan the content structures, and build header
1417 ** fields that will need to be output into the
1422 build_headers(CT ct)
1424 int cc, mailbody, len;
1426 char *np, *vp, buffer[BUFSIZ];
1427 CI ci = &ct->c_ctinfo;
1430 ** If message is type multipart, then add the multipart
1431 ** boundary to the list of attribute/value pairs.
1433 if (ct->c_type == CT_MULTIPART) {
1435 static int level = 0; /* store nesting level */
1439 snprintf(buffer, sizeof(buffer), "boundary=%s%d",
1441 cp = strchr(*ap++ = getcpy(buffer), '=');
1448 ** Skip the output of Content-Type, parameters, content
1449 ** description and disposition, and Content-ID if the
1450 ** content is of type "message" and the rfc934 compatibility
1451 ** flag is set (which means we are inside multipart/digest
1452 ** and the switch -rfc934mode was given).
1454 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1458 ** output the content type and subtype
1460 np = getcpy(TYPE_FIELD);
1461 vp = concat(" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1463 /* keep track of length of line */
1464 len = strlen(TYPE_FIELD) + strlen(ci->ci_type) +
1465 strlen(ci->ci_subtype) + 3;
1467 mailbody = ct->c_type == CT_MESSAGE &&
1468 ct->c_subtype == MESSAGE_EXTERNAL &&
1469 ((struct exbody *) ct->c_ctparams)->eb_body;
1472 ** Append the attribute/value pairs to
1473 ** the end of the Content-Type line.
1475 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1476 if (mailbody && !mh_strcasecmp(*ap, "body"))
1482 snprintf(buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1483 if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) {
1484 vp = add("\n\t", vp);
1490 vp = add(buffer, vp);
1495 ** Append any RFC-822 comment to the end of
1496 ** the Content-Type line.
1498 if (ci->ci_comment) {
1499 snprintf(buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1500 if (len + 1 + (cc = 2 + strlen(ci->ci_comment)) >= CPERLIN) {
1501 vp = add("\n\t", vp);
1507 vp = add(buffer, vp);
1511 add_header(ct, np, vp);
1514 ** output the Content-ID, unless disabled by -nocontentid
1516 if (contentidsw && ct->c_id) {
1517 np = getcpy(ID_FIELD);
1518 vp = concat(" ", ct->c_id, NULL);
1519 add_header(ct, np, vp);
1523 ** output the Content-Description
1526 np = getcpy(DESCR_FIELD);
1527 vp = concat(" ", ct->c_descr, NULL);
1528 add_header(ct, np, vp);
1532 ** output the Content-Disposition
1535 np = getcpy(DISPO_FIELD);
1536 vp = concat(" ", ct->c_dispo, NULL);
1537 add_header(ct, np, vp);
1542 ** If this is the internal content structure for a
1543 ** "message/external", then we are done with the
1544 ** headers (since it has no body).
1550 ** output the Content-MD5
1553 np = getcpy(MD5_FIELD);
1554 vp = calculate_digest(ct, (ct->c_encoding == CE_QUOTED) ?
1556 add_header(ct, np, vp);
1560 ** output the Content-Transfer-Encoding
1562 switch (ct->c_encoding) {
1564 /* Nothing to output */
1566 np = getcpy(ENCODING_FIELD);
1567 vp = concat(" ", "7bit", "\n", NULL);
1568 add_header(ct, np, vp);
1573 if (ct->c_type == CT_MESSAGE)
1574 adios(NULL, "internal error, invalid encoding");
1576 np = getcpy(ENCODING_FIELD);
1577 vp = concat(" ", "8bit", "\n", NULL);
1578 add_header(ct, np, vp);
1582 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1583 adios(NULL, "internal error, invalid encoding");
1585 np = getcpy(ENCODING_FIELD);
1586 vp = concat(" ", "quoted-printable", "\n", NULL);
1587 add_header(ct, np, vp);
1591 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1592 adios(NULL, "internal error, invalid encoding");
1594 np = getcpy(ENCODING_FIELD);
1595 vp = concat(" ", "base64", "\n", NULL);
1596 add_header(ct, np, vp);
1600 if (ct->c_type == CT_MESSAGE)
1601 adios(NULL, "internal error, invalid encoding");
1603 np = getcpy(ENCODING_FIELD);
1604 vp = concat(" ", "binary", "\n", NULL);
1605 add_header(ct, np, vp);
1609 adios(NULL, "unknown transfer encoding in content");
1614 ** Additional content specific header processing
1616 switch (ct->c_type) {
1619 struct multipart *m;
1622 m = (struct multipart *) ct->c_ctparams;
1623 for (part = m->mp_parts; part; part = part->mp_next) {
1633 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1636 e = (struct exbody *) ct->c_ctparams;
1637 build_headers(e->eb_content);
1650 static char nib2b64[0x40+1] =
1651 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1654 calculate_digest(CT ct, int asciiP)
1657 char buffer[BUFSIZ], *vp, *op;
1659 unsigned char digest[16];
1660 unsigned char outbuf[25];
1663 CE ce = ct->c_cefile;
1666 if ((in = fopen(ce->ce_file, "r")) == NULL)
1667 adios(ce->ce_file, "unable to open for reading");
1669 /* Initialize md5 context */
1670 MD5Init(&mdContext);
1672 /* calculate md5 message digest */
1674 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1677 cp = buffer + strlen(buffer) - 1;
1678 if ((c = *cp) == '\n')
1681 MD5Update(&mdContext, (unsigned char *) buffer,
1682 (unsigned int) strlen(buffer));
1685 MD5Update(&mdContext, (unsigned char *) "\r\n",
1689 while ((cc = fread(buffer, sizeof(*buffer), sizeof(buffer),
1691 MD5Update(&mdContext, (unsigned char *) buffer,
1695 /* md5 finalization. Write digest and zero md5 context */
1696 MD5Final(digest, &mdContext);
1701 /* print debugging info */
1705 fprintf(stderr, "MD5 digest=");
1706 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1708 fprintf(stderr, "%02x", *dp & 0xff);
1709 fprintf(stderr, "\n");
1712 /* encode the digest using base64 */
1713 for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
1714 cc > 0; cc -= 3, op += 4) {
1718 bits = (*dp++ & 0xff) << 16;
1720 bits |= (*dp++ & 0xff) << 8;
1722 bits |= *dp++ & 0xff;
1725 for (bp = op + 4; bp > op; bits >>= 6)
1726 *--bp = nib2b64[bits & 0x3f];
1734 /* null terminate string */
1737 /* now make copy and return string */
1738 vp = concat(" ", outbuf, "\n", NULL);