2 ** mhbuild.c -- 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). But the code still has some of
12 ** the mhn.c code in it. This program needs additional
13 ** streamlining and removal of unneeded code.
18 #include <h/signals.h>
24 #include <h/mhparse.h>
25 #include <h/mhcachesbr.h>
28 #ifdef HAVE_SYS_TIME_H
29 # include <sys/time.h>
33 static struct swit switches[] = {
41 { "noebcdicsafe", 0 },
57 { "norfc934mode", 0 },
63 { "rcache policy", 0 },
65 { "wcache policy", 0 },
66 #define CONTENTIDSW 16
68 #define NCONTENTIDSW 17
81 ** Directory to place tmp files. This must
82 ** be set before these routines are called.
88 static char prefix[] = "----- =_aaaaaaaaaa";
96 int make_intermediates(char *);
97 void content_error(char *, CT, char *, ...);
100 int find_cache(CT, int, int *, char *, char *, int);
103 int ftp_get(char *, char *, char *, char *, char *, char *, int, int);
106 void free_content(CT);
107 void free_ctinfo(CT);
108 void free_encoding(CT, int);
113 static int init_decoded_content(CT);
114 static char *fgetstr(char *, int, FILE *);
115 static int user_content(FILE *, char *, char *, CT *);
116 static void set_id(CT, int);
117 static int compose_content(CT);
118 static int scan_content(CT);
119 static int build_headers(CT);
120 static char *calculate_digest(CT, int);
121 static CT build_mime(char *);
128 extern char *cache_public;
129 extern char *cache_private;
142 static char infile[BUFSIZ];
143 static int unlink_infile = 0;
145 static char outfile[BUFSIZ];
146 static int unlink_outfile = 0;
148 static void unlink_done(int) NORETURN;
151 int output_message(CT, char *);
152 int output_message_fp(CT, FILE *, char*);
155 int list_all_messages(CT *, int, int, int, int);
158 void set_endian(void);
161 void free_content(CT);
165 main(int argc, char **argv)
167 int sizesw = 1, headsw = 1;
169 char *cp, buf[BUFSIZ];
170 char buffer[BUFSIZ], *compfile = NULL;
171 char **argp, **arguments;
179 setlocale(LC_ALL, "");
181 invo_name = mhbasename(argv[0]);
183 /* read user profile/context */
186 arguments = getarguments(invo_name, argc, argv, 1);
189 while ((cp = *argp++)) {
190 if (cp[0] == '-' && cp[1] == '\0') {
192 adios(NULL, "cannot specify both standard input and a file");
195 listsw = 0; /* turn off -list if using std in/out */
196 verbosw = 0; /* turn off -verbose listings */
200 switch (smatch(++cp, switches)) {
202 ambigsw(cp, switches);
205 adios(NULL, "-%s unknown", cp);
208 snprintf(buf, sizeof(buf), "%s [switches] file", invo_name);
209 print_help(buf, switches, 1);
212 print_version(invo_name);
216 icachesw = &rcachesw;
219 icachesw = &wcachesw;
221 if (!(cp = *argp++) || *cp == '-')
222 adios(NULL, "missing argument to %s",
224 switch (*icachesw = smatch(cp, caches)) {
229 adios(NULL, "%s unknown", cp);
296 adios(NULL, "only one composition file allowed");
303 if ((cp = getenv("MM_NOASK")) && strcmp(cp, "1")==0)
307 ** Check if we've specified an additional profile
309 if ((cp = getenv("MHBUILD"))) {
310 if ((fp = fopen(cp, "r"))) {
311 readconfig((struct node **) 0, fp, cp, 0);
314 admonish("", "unable to read $MHBUILD profile (%s)",
320 ** Read the standard profile setup
322 if ((fp = fopen(cp = etcpath("mhn.defaults"), "r"))) {
323 readconfig((struct node **) 0, fp, cp, 0);
327 /* Check for public cache location */
328 if ((cache_public = context_find(nmhcache)) && *cache_public != '/')
331 /* Check for private cache location */
332 if (!(cache_private = context_find(nmhprivcache)))
333 cache_private = ".cache";
334 cache_private = getcpy(toabsdir(cache_private));
337 ** Check for storage directory. If defined, we
338 ** will store temporary files there. Else we
339 ** store them in standard nmh directory.
341 if ((cp = context_find(nmhstorage)) && *cp)
342 tmp = concat(cp, "/", invo_name, NULL);
344 tmp = getcpy(toabsdir(invo_name));
346 /* Check if we have a file to process */
348 adios(NULL, "need to specify a %s composition file",
352 ** Process the composition file from standard input.
354 if (compfile[0] == '-' && compfile[1] == '\0') {
355 /* copy standard input to temporary file */
356 strncpy(infile, m_mktemp(invo_name, NULL, &fp),
358 while (fgets(buffer, BUFSIZ, stdin))
363 /* build the content structures for MIME message */
364 ct = build_mime(infile);
368 /* output MIME message to this temporary file */
369 strncpy(outfile, m_mktemp(invo_name, NULL, &fp_out),
373 /* output the message */
374 output_message_fp(ct, fp_out, outfile);
377 /* output the temp file to standard output */
378 if ((fp = fopen(outfile, "r")) == NULL)
379 adios(outfile, "unable to open");
380 while (fgets(buffer, BUFSIZ, fp))
381 fputs(buffer, stdout);
395 ** Process the composition file from a file.
398 /* build the content structures for MIME message */
399 ct = build_mime(compfile);
403 /* output MIME message to this temporary file */
404 strncpy(outfile, m_mktemp2(compfile, invo_name, NULL, &fp_out),
408 /* output the message */
409 output_message_fp(ct, fp_out, outfile);
413 ** List the message info
416 list_all_messages(cts, headsw, sizesw, verbosw, debugsw);
418 /* Rename composition draft */
419 snprintf(buffer, sizeof(buffer), "%s.orig", m_backup(compfile));
420 if (rename(compfile, buffer) == NOTOK) {
421 adios(compfile, "unable to rename comp draft %s to", buffer);
424 /* Rename output file to take its place */
425 if (rename(outfile, compfile) == NOTOK) {
426 advise(outfile, "unable to rename output %s to", compfile);
427 rename(buffer, compfile);
439 unlink_done(int status)
442 ** Check if we need to remove stray temporary files.
453 ** Main routine for translating composition file
454 ** into valid MIME message. It translates the draft
455 ** into a content structure (actually a tree of content
456 ** structures). This message then can be manipulated
457 ** in various ways, including being output via
461 build_mime(char *infile)
464 char buf[BUFSIZ], name[NAMESZ];
473 /* open the composition draft */
474 if ((in = fopen(infile, "r")) == NULL)
475 adios(infile, "unable to open for reading");
478 ** Allocate space for primary (outside) content
480 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
481 adios(NULL, "out of memory");
484 ** Allocate structure for handling decoded content
485 ** for this part. We don't really need this, but
486 ** allocate it to remain consistent.
488 init_decoded_content(ct);
491 ** Parse some of the header fields in the composition
492 ** draft into the linked list of header fields for
493 ** the new MIME message.
495 for (compnum = 1, state = FLD;;) {
496 switch (state = m_getfld(state, name, buf, sizeof(buf), in)) {
502 /* abort if draft has Mime-Version header field */
503 if (!mh_strcasecmp(name, VRSN_FIELD))
504 adios(NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
507 ** abort if draft has Content-Transfer-Encoding
510 if (!mh_strcasecmp(name, ENCODING_FIELD))
511 adios(NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
513 /* ignore any Content-Type fields in the header */
514 if (!mh_strcasecmp(name, TYPE_FIELD)) {
515 while (state == FLDPLUS)
516 state = m_getfld(state, name, buf,
521 /* get copies of the buffers */
525 /* if necessary, get rest of field */
526 while (state == FLDPLUS) {
527 state = m_getfld(state, name, buf,
529 vp = add(buf, vp); /* add to prev value */
532 /* Now add the header data to the list */
533 add_header(ct, np, vp);
536 /* if this wasn't the last hdr field, then continue */
542 adios(NULL, "draft has empty body -- no directives!");
547 fseek(in, (long) (-strlen(buf)), SEEK_CUR);
552 adios(NULL, "message format error in component #%d",
556 adios(NULL, "getfld() returned %d", state);
562 ** Now add the MIME-Version header field
563 ** to the list of header fields.
565 np = getcpy(VRSN_FIELD);
566 vp = concat(" ", VRSN_VALUE, "\n", NULL);
567 add_header(ct, np, vp);
570 ** We initally assume we will find multiple contents in the
571 ** draft. So create a multipart/mixed content to hold everything.
572 ** We can remove this later, if it is not needed.
574 if (get_ctinfo("multipart/mixed", ct, 0) == NOTOK)
576 ct->c_type = CT_MULTIPART;
577 ct->c_subtype = MULTI_MIXED;
578 ct->c_file = getcpy(infile);
580 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
581 adios(NULL, "out of memory");
582 ct->c_ctparams = (void *) m;
586 ** read and parse the composition file
587 ** and the directives it contains.
589 while (fgetstr(buf, sizeof(buf) - 1, in)) {
593 if (user_content(in, infile, buf, &p) == DONE) {
594 admonish(NULL, "ignoring spurious #end");
600 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
601 adios(NULL, "out of memory");
608 ** close the composition draft since
609 ** it's not needed any longer.
613 /* check if any contents were found */
615 adios(NULL, "no content directives found");
618 ** If only one content was found, then remove and
619 ** free the outer multipart content.
621 if (!m->mp_parts->mp_next) {
624 p = m->mp_parts->mp_part;
625 m->mp_parts->mp_part = NULL;
627 /* move header fields */
628 p->c_first_hf = ct->c_first_hf;
629 p->c_last_hf = ct->c_last_hf;
630 ct->c_first_hf = NULL;
631 ct->c_last_hf = NULL;
640 ** Fill out, or expand directives. Parse and execute
641 ** commands specified by profile composition strings.
645 if ((cp = strchr(prefix, 'a')) == NULL)
646 adios(NULL, "internal error(4)");
649 ** Scan the contents. Choose a transfer encoding, and
650 ** check if prefix for multipart boundary clashes with
651 ** any of the contents.
653 while (scan_content(ct) == NOTOK) {
658 adios(NULL, "giving up trying to find a unique delimiter string");
664 /* Build the rest of the header field structures */
672 ** Set up structures for placing unencoded
673 ** content when building parts.
677 init_decoded_content(CT ct)
681 if ((ce = (CE) calloc(1, sizeof(*ce))) == NULL)
682 adios(NULL, "out of memory");
685 ct->c_ceopenfnx = open7Bit; /* since unencoded */
686 ct->c_ceclosefnx = close_encoding;
687 ct->c_cesizefnx = NULL; /* since unencoded */
694 fgetstr(char *s, int n, FILE *stream)
698 for (ep = (cp = s) + n; cp < ep; ) {
701 if (!fgets(cp, n, stream))
702 return (cp != s ? s : NULL);
703 if (cp == s && *cp != '#')
706 cp += (i = strlen(cp)) - 1;
707 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
718 ** Parse the composition draft for text and directives.
719 ** Do initial setup of Content structure.
723 user_content(FILE *in, char *file, char *buf, CT *ctp)
732 struct str2init *s2i;
737 if (buf[0] == '\n' || strcmp(buf, "#\n") == 0) {
742 /* allocate basic Content structure */
743 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
744 adios(NULL, "out of memory");
747 /* allocate basic structure for handling decoded content */
748 init_decoded_content(ct);
755 ** Handle inline text. Check if line
756 ** is one of the following forms:
758 ** 1) doesn't begin with '#' (implicit directive)
759 ** 2) begins with "##" (implicit directive)
760 ** 3) begins with "#<"
762 if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
766 char content[BUFSIZ];
770 cp = m_mktemp2(NULL, invo_name, NULL, &out);
772 adios("mhbuild", "unable to create temporary file");
774 /* use a temp file to collect the plain text lines */
775 ce->ce_file = getcpy(cp);
778 if (buf[0] == '#' && buf[1] == '<') {
779 strncpy(content, buf + 2, sizeof(content));
786 /* the directive is implicit */
787 strncpy(content, "text/plain", sizeof(content));
789 strncpy(buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
793 if (headers >= 0 && uprf(buffer, DESCR_FIELD) &&
794 buffer[i=strlen(DESCR_FIELD)] == ':') {
798 ct->c_descr = add(buffer + i + 1, ct->c_descr);
799 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
800 adios(NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
808 adios(NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
816 if (headers >= 0 && uprf(buffer, DISPO_FIELD)
817 && buffer[i = strlen(DISPO_FIELD)] == ':') {
821 ct->c_dispo = add(buffer + i + 1, ct->c_dispo);
822 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
823 adios(NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
831 adios(NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
839 if (headers != 1 || buffer[0] != '\n')
845 if ((cp = fgetstr(buffer, sizeof(buffer) - 1, in))
848 if (buffer[0] == '#') {
851 if (buffer[1] != '#')
853 for (cp = (bp = buffer) + 1; *cp; cp++)
860 ct->c_end = ftell(out);
863 /* parse content type */
864 if (get_ctinfo(content, ct, inlineD) == NOTOK)
867 for (s2i = str2cts; s2i->si_key; s2i++)
868 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
870 if (!s2i->si_key && !uprf(ci->ci_type, "X-"))
874 ** check type specified (possibly implicitly)
876 switch (ct->c_type = s2i->si_val) {
878 if (!mh_strcasecmp(ci->ci_subtype, "rfc822")) {
879 ct->c_encoding = CE_7BIT;
884 adios(NULL, "it doesn't make sense to define an in-line %s content",
885 ct->c_type == CT_MESSAGE ? "message" :
891 if ((ct->c_ctinitfnx = s2i->si_init))
892 (*ct->c_ctinitfnx) (ct);
897 fseek(in, pos, SEEK_SET);
902 ** If we've reached this point, the next line
903 ** must be some type of explicit directive.
906 /* check if directive is external-type */
907 extrnal = (buf[1] == '@');
909 /* parse directive */
910 if (get_ctinfo(buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
913 /* check directive against the list of MIME types */
914 for (s2i = str2cts; s2i->si_key; s2i++)
915 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
919 ** Check if the directive specified a valid type.
920 ** This will happen if it was one of the following forms:
922 ** #type/subtype (or)
927 adios(NULL, "missing subtype in \"#%s\"", ci->ci_type);
929 switch (ct->c_type = s2i->si_val) {
931 adios(NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
935 if (!mh_strcasecmp(ci->ci_subtype, "partial"))
936 adios(NULL, "sorry, \"#%s/%s\" isn't supported", ci->ci_type, ci->ci_subtype);
937 if (!mh_strcasecmp(ci->ci_subtype, "external-body"))
938 adios(NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
940 adios(NULL, "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
944 if ((ct->c_ctinitfnx = s2i->si_init))
945 (*ct->c_ctinitfnx) (ct);
950 ** #@type/subtype (external types directive)
957 adios(NULL, "need external information for \"#@%s/%s\"", ci->ci_type, ci->ci_subtype);
960 snprintf(buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
965 ** Since we are using the current Content structure to
966 ** hold information about the type of the external
967 ** reference, we need to create another Content
968 ** structure for the message/external-body to wrap
971 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
972 adios(NULL, "out of memory");
975 if (get_ctinfo(buffer, ct, 0) == NOTOK)
977 ct->c_type = CT_MESSAGE;
978 ct->c_subtype = MESSAGE_EXTERNAL;
980 if ((e = (struct exbody *)
981 calloc(1, sizeof(*e))) == NULL)
982 adios(NULL, "out of memory");
983 ct->c_ctparams = (void *) e;
989 if (params_external(ct, 1) == NOTOK)
995 /* Handle [file] argument */
997 /* check if specifies command to execute */
998 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
999 for (cp = ci->ci_magic + 1; isspace(*cp); cp++)
1002 adios(NULL, "empty pipe command for #%s directive", ci->ci_type);
1007 /* record filename of decoded contents */
1008 ce->ce_file = ci->ci_magic;
1009 if (access(ce->ce_file, R_OK) == NOTOK)
1010 adios("reading", "unable to access %s for", ce->ce_file);
1011 if (listsw && stat(ce->ce_file, &st) != NOTOK)
1012 ct->c_end = (long) st.st_size;
1013 ci->ci_magic = NULL;
1019 ** No [file] argument, so check profile for
1020 ** method to compose content.
1022 snprintf(buffer, sizeof(buffer), "%s-compose-%s/%s",
1023 invo_name, ci->ci_type, ci->ci_subtype);
1024 if ((cp = context_find(buffer)) == NULL || *cp == '\0') {
1025 snprintf(buffer, sizeof(buffer), "%s-compose-%s",
1026 invo_name, ci->ci_type);
1027 if ((cp = context_find(buffer)) == NULL ||
1029 content_error(NULL, ct, "don't know how to compose content");
1033 ci->ci_magic = getcpy(cp);
1038 adios(NULL, "external definition not allowed for \"#%s\"",
1042 ** Message directive
1043 ** #forw [+folder] [msgs]
1045 if (!mh_strcasecmp(ci->ci_type, "forw")) {
1047 char *folder, *arguments[MAXARGS];
1053 ap = brkstring(ci->ci_magic, " ", "\n");
1054 for (i=0; ap[i] && i<MAXARGS-1; i++) {
1055 arguments[i] = ap[i];
1057 arguments[i] = NULL;
1060 arguments[0] = seq_cur;
1061 arguments[1] = NULL;
1065 /* search the arguments for a folder name */
1066 for (ap = arguments; *ap; ap++) {
1068 if (*cp == '+' || *cp == '@') {
1070 adios(NULL, "only one folder per #forw directive");
1072 folder = getcpy(expandfol(cp));
1076 /* else, use the current folder */
1078 folder = getcpy(getcurfol());
1080 if (!(mp = folder_read(folder)))
1081 adios(NULL, "unable to read folder %s", folder);
1082 for (ap = arguments; *ap; ap++) {
1084 if (*cp != '+' && *cp != '@')
1085 if (!m_convert(mp, cp))
1092 ** If there is more than one message to include, make this
1093 ** a content of type "multipart/digest" and insert each message
1094 ** as a subpart. If there is only one message, then make this
1095 ** a content of type "message/rfc822".
1097 if (mp->numsel > 1) {
1098 /* we are forwarding multiple messages */
1099 if (get_ctinfo("multipart/digest", ct, 0) == NOTOK)
1101 ct->c_type = CT_MULTIPART;
1102 ct->c_subtype = MULTI_DIGEST;
1104 if ((m = (struct multipart *)
1105 calloc(1, sizeof(*m))) == NULL)
1106 adios(NULL, "out of memory");
1107 ct->c_ctparams = (void *) m;
1110 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
1111 if (is_selected(mp, msgnum)) {
1116 if ((p = (CT) calloc(1, sizeof(*p)))
1118 adios(NULL, "out of memory");
1119 init_decoded_content(p);
1121 if (get_ctinfo("message/rfc822", p, 0)
1124 p->c_type = CT_MESSAGE;
1125 p->c_subtype = MESSAGE_RFC822;
1127 snprintf(buffer, sizeof(buffer),
1128 "%s/%d", mp->foldpath,
1130 pe->ce_file = getcpy(buffer);
1131 if (listsw && stat(pe->ce_file, &st)
1133 p->c_end = (long) st.st_size;
1135 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
1136 adios(NULL, "out of memory");
1138 pp = &part->mp_next;
1143 /* we are forwarding one message */
1144 if (get_ctinfo("message/rfc822", ct, 0) == NOTOK)
1146 ct->c_type = CT_MESSAGE;
1147 ct->c_subtype = MESSAGE_RFC822;
1149 msgnum = mp->lowsel;
1150 snprintf(buffer, sizeof(buffer), "%s/%d",
1151 mp->foldpath, msgnum);
1152 ce->ce_file = getcpy(buffer);
1153 if (listsw && stat(ce->ce_file, &st) != NOTOK)
1154 ct->c_end = (long) st.st_size;
1157 folder_free(mp); /* free folder/message structure */
1164 if (!mh_strcasecmp(ci->ci_type, "end")) {
1171 ** #begin [ alternative | parallel ]
1173 if (!mh_strcasecmp(ci->ci_type, "begin")) {
1174 if (!ci->ci_magic) {
1176 cp = SubMultiPart[vrsn - 1].kv_key;
1177 } else if (!mh_strcasecmp(ci->ci_magic, "alternative")) {
1178 vrsn = MULTI_ALTERNATE;
1179 cp = SubMultiPart[vrsn - 1].kv_key;
1180 } else if (!mh_strcasecmp(ci->ci_magic, "parallel")) {
1181 vrsn = MULTI_PARALLEL;
1182 cp = SubMultiPart[vrsn - 1].kv_key;
1183 } else if (uprf(ci->ci_magic, "digest")) {
1186 vrsn = MULTI_UNKNOWN;
1191 snprintf(buffer, sizeof(buffer), "multipart/%s", cp);
1192 if (get_ctinfo(buffer, ct, 0) == NOTOK)
1194 ct->c_type = CT_MULTIPART;
1195 ct->c_subtype = vrsn;
1197 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
1198 adios(NULL, "out of memory");
1199 ct->c_ctparams = (void *) m;
1202 while (fgetstr(buffer, sizeof(buffer) - 1, in)) {
1206 if (user_content(in, file, buffer, &p) == DONE) {
1208 adios(NULL, "empty \"#begin ... #end\" sequence");
1214 if ((part = (struct part *)
1215 calloc(1, sizeof(*part))) == NULL)
1216 adios(NULL, "out of memory");
1218 pp = &part->mp_next;
1221 admonish(NULL, "premature end-of-file, missing #end");
1226 ** Unknown directive
1228 adios(NULL, "unknown directive \"#%s\"", ci->ci_type);
1229 return NOTOK; /* NOT REACHED */
1234 set_id(CT ct, int top)
1238 static time_t clock = 0;
1239 static char *msgfmt;
1243 snprintf(msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
1244 (int) getpid(), (long) clock, LocalName());
1246 msgfmt = getcpy(msgid);
1248 snprintf(msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
1249 ct->c_id = getcpy(msgid);
1253 static char ebcdicsafe[0x100] = {
1254 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1255 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1256 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1257 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1258 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
1259 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1260 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1261 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1262 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1263 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1264 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1265 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
1266 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1267 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1268 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1269 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1270 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1271 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1272 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1273 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1274 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1275 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1276 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1277 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1278 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1279 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1280 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1281 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1282 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1283 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1284 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1285 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1290 ** Fill out, or expand the various contents in the composition
1291 ** draft. Read-in any necessary files. Parse and execute any
1292 ** commands specified by profile composition strings.
1296 compose_content(CT ct)
1298 CE ce = ct->c_cefile;
1300 switch (ct->c_type) {
1305 char partnam[BUFSIZ];
1306 struct multipart *m = (struct multipart *) ct->c_ctparams;
1310 snprintf(partnam, sizeof(partnam), "%s.",
1312 pp = partnam + strlen(partnam);
1317 /* first, we call compose_content on all the subparts */
1318 for (part = m->mp_parts, partnum = 1; part;
1319 part = part->mp_next, partnum++) {
1320 CT p = part->mp_part;
1322 sprintf(pp, "%d", partnum);
1323 p->c_partno = getcpy(partnam);
1324 if (compose_content(p) == NOTOK)
1329 ** If the -rfc934mode switch is given, then check all
1330 ** the subparts of a multipart/digest. If they are all
1331 ** message/rfc822, then mark this content and all
1332 ** subparts with the rfc934 compatibility mode flag.
1334 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
1337 for (part = m->mp_parts; part; part = part->mp_next) {
1338 CT p = part->mp_part;
1340 if (p->c_subtype != MESSAGE_RFC822) {
1345 ct->c_rfc934 = is934;
1346 for (part = m->mp_parts; part; part = part->mp_next) {
1347 CT p = part->mp_part;
1349 if ((p->c_rfc934 = is934))
1355 ct->c_end = (partnum = strlen(prefix) + 2) + 2;
1359 for (part = m->mp_parts; part; part = part->mp_next)
1360 ct->c_end += part->mp_part->c_end + partnum;
1366 /* Nothing to do for type message */
1370 ** Discrete types (text/application/audio/image/video)
1375 int xstdout, len, buflen;
1376 char *bp, **ap, *cp;
1377 char *vec[4], buffer[BUFSIZ];
1379 CI ci = &ct->c_ctinfo;
1382 if (!(cp = ci->ci_magic))
1383 adios(NULL, "internal error(5)");
1385 tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1386 if (tfile == NULL) {
1387 adios("mhbuild", "unable to create temporary file");
1389 ce->ce_file = getcpy(tfile);
1394 /* Get buffer ready to go */
1397 buflen = sizeof(buffer);
1400 ** Parse composition string into buffer
1402 for ( ; *cp; cp++) {
1408 ** insert parameters from
1414 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1415 snprintf(bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1425 /* %f, and stdout is not-redirected */
1431 ** insert temporary filename
1432 ** where content should be
1435 snprintf(bp, buflen, "%s", ce->ce_file);
1439 /* insert content subtype */
1440 strncpy(bp, ci->ci_subtype, buflen);
1444 /* insert character % */
1465 printf("composing content %s/%s from command\n\t%s\n", ci->ci_type, ci->ci_subtype, buffer);
1467 fflush(stdout); /* not sure if need for -noverbose */
1474 if ((out = fopen(ce->ce_file, "w")) == NULL)
1475 adios(ce->ce_file, "unable to open for writing");
1477 switch (child_id = fork()) {
1479 adios("fork", "unable to fork");
1484 dup2(fileno(out), 1);
1486 execvp("/bin/sh", vec);
1487 fprintf(stderr, "unable to exec ");
1494 if (pidXwait(child_id, NULL))
1500 /* Check size of file */
1501 if (listsw && ct->c_end == 0L) {
1504 if (stat(ce->ce_file, &st) != NOTOK)
1505 ct->c_end = (long) st.st_size;
1515 ** Scan the content.
1517 ** 1) choose a transfer encoding.
1518 ** 2) check for clashes with multipart boundary string.
1519 ** 3) for text content, figure out which character set is being used.
1521 ** If there is a clash with one of the contents and the multipart boundary,
1522 ** this function will exit with NOTOK. This will cause the scanning process
1523 ** to be repeated with a different multipart boundary. It is possible
1524 ** (although highly unlikely) that this scan will be repeated multiple times.
1531 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1532 int checklinelen = 0, linelen = 0; /* check for long lines */
1533 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1534 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1535 int checkebcdic = 0, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
1536 unsigned char *cp = NULL, buffer[BUFSIZ];
1537 struct text *t = NULL;
1539 CE ce = ct->c_cefile;
1542 ** handle multipart by scanning all subparts
1543 ** and then checking their encoding.
1545 if (ct->c_type == CT_MULTIPART) {
1546 struct multipart *m = (struct multipart *) ct->c_ctparams;
1549 /* initially mark the domain of enclosing multipart as 7bit */
1550 ct->c_encoding = CE_7BIT;
1552 for (part = m->mp_parts; part; part = part->mp_next) {
1553 CT p = part->mp_part;
1555 if (scan_content(p) == NOTOK) {
1556 /* choose encoding for subpart */
1561 ** if necessary, enlarge encoding for enclosing
1564 if (p->c_encoding == CE_BINARY)
1565 ct->c_encoding = CE_BINARY;
1566 if (p->c_encoding == CE_8BIT &&
1567 ct->c_encoding != CE_BINARY)
1568 ct->c_encoding = CE_8BIT;
1575 ** Decide what to check while scanning this content.
1577 switch (ct->c_type) {
1581 if (ct->c_subtype == TEXT_PLAIN) {
1586 checkebcdic = ebcdicsw;
1592 case CT_APPLICATION:
1594 checkebcdic = ebcdicsw;
1606 /* don't check anything for message/external */
1607 if (ct->c_subtype == MESSAGE_EXTERNAL)
1617 ** Don't check anything for these types,
1618 ** since we are forcing use of base64.
1629 ** Scan the unencoded content
1631 if (check8bit || checklinelen || checklinespace || checkboundary) {
1632 if ((in = fopen(ce->ce_file, "r")) == NULL)
1633 adios(ce->ce_file, "unable to open for reading");
1634 len = strlen(prefix);
1636 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1638 ** Check for 8bit data.
1641 for (cp = buffer; *cp; cp++) {
1642 if (!isascii(*cp)) {
1644 /* no need to keep checking */
1648 ** Check if character is ebcdic-safe.
1649 ** We only check this if also checking
1652 if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1654 /* no need to keep checking */
1661 ** Check line length.
1663 if (checklinelen && (strlen(buffer) > CPERLIN + 1)) {
1665 checklinelen = 0; /* no need to keep checking */
1669 ** Check if line ends with a space.
1671 if (checklinespace &&
1672 (cp = buffer + strlen(buffer) - 2) >
1673 buffer && isspace(*cp)) {
1675 /* no need to keep checking */
1680 ** Check if content contains a line that clashes
1681 ** with our standard boundary for multipart messages.
1683 if (checkboundary && buffer[0] == '-' &&
1685 for (cp = buffer + strlen(buffer) - 1;
1690 if (strncmp(buffer + 2, prefix, len)==0 &&
1691 isdigit(buffer[2 + len])) {
1693 /* no need to keep checking */
1702 ** Decide which transfer encoding to use.
1704 switch (ct->c_type) {
1707 ** If the text content didn't specify a character
1708 ** set, we need to figure out which one was used.
1710 t = (struct text *) ct->c_ctparams;
1711 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1712 CI ci = &ct->c_ctinfo;
1715 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1719 t->tx_charset = CHARSET_UNKNOWN;
1720 *ap = concat("charset=", write_charset_8bit(),
1723 t->tx_charset = CHARSET_USASCII;
1724 *ap = getcpy("charset=us-ascii");
1727 cp = strchr(*ap++, '=');
1733 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1735 ct->c_encoding = CE_QUOTED;
1737 ct->c_encoding = CE_7BIT;
1740 case CT_APPLICATION:
1741 /* For application type, use base64, except when postscript */
1742 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1744 ct->c_encoding = (ct->c_subtype ==
1745 APPLICATION_POSTSCRIPT) ?
1746 CE_QUOTED : CE_BASE64;
1748 ct->c_encoding = CE_7BIT;
1752 ct->c_encoding = CE_7BIT;
1758 /* For audio, image, and video contents, just use base64 */
1759 ct->c_encoding = CE_BASE64;
1763 return (boundaryclash ? NOTOK : OK);
1768 ** Scan the content structures, and build header
1769 ** fields that will need to be output into the
1774 build_headers(CT ct)
1776 int cc, mailbody, len;
1778 char *np, *vp, buffer[BUFSIZ];
1779 CI ci = &ct->c_ctinfo;
1782 ** If message is type multipart, then add the multipart
1783 ** boundary to the list of attribute/value pairs.
1785 if (ct->c_type == CT_MULTIPART) {
1787 static int level = 0; /* store nesting level */
1791 snprintf(buffer, sizeof(buffer), "boundary=%s%d",
1793 cp = strchr(*ap++ = getcpy(buffer), '=');
1800 ** Skip the output of Content-Type, parameters, content
1801 ** description and disposition, and Content-ID if the
1802 ** content is of type "message" and the rfc934 compatibility
1803 ** flag is set (which means we are inside multipart/digest
1804 ** and the switch -rfc934mode was given).
1806 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1810 ** output the content type and subtype
1812 np = getcpy(TYPE_FIELD);
1813 vp = concat(" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1815 /* keep track of length of line */
1816 len = strlen(TYPE_FIELD) + strlen(ci->ci_type) +
1817 strlen(ci->ci_subtype) + 3;
1819 mailbody = ct->c_type == CT_MESSAGE &&
1820 ct->c_subtype == MESSAGE_EXTERNAL &&
1821 ((struct exbody *) ct->c_ctparams)->eb_body;
1824 ** Append the attribute/value pairs to
1825 ** the end of the Content-Type line.
1827 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1828 if (mailbody && !mh_strcasecmp(*ap, "body"))
1834 snprintf(buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1835 if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) {
1836 vp = add("\n\t", vp);
1842 vp = add(buffer, vp);
1847 ** Append any RFC-822 comment to the end of
1848 ** the Content-Type line.
1850 if (ci->ci_comment) {
1851 snprintf(buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1852 if (len + 1 + (cc = 2 + strlen(ci->ci_comment)) >= CPERLIN) {
1853 vp = add("\n\t", vp);
1859 vp = add(buffer, vp);
1863 add_header(ct, np, vp);
1866 ** output the Content-ID, unless disabled by -nocontentid
1868 if (contentidsw && ct->c_id) {
1869 np = getcpy(ID_FIELD);
1870 vp = concat(" ", ct->c_id, NULL);
1871 add_header(ct, np, vp);
1875 ** output the Content-Description
1878 np = getcpy(DESCR_FIELD);
1879 vp = concat(" ", ct->c_descr, NULL);
1880 add_header(ct, np, vp);
1884 ** output the Content-Disposition
1887 np = getcpy(DISPO_FIELD);
1888 vp = concat(" ", ct->c_dispo, NULL);
1889 add_header(ct, np, vp);
1894 ** If this is the internal content structure for a
1895 ** "message/external", then we are done with the
1896 ** headers (since it has no body).
1902 ** output the Content-MD5
1905 np = getcpy(MD5_FIELD);
1906 vp = calculate_digest(ct, (ct->c_encoding == CE_QUOTED) ?
1908 add_header(ct, np, vp);
1912 ** output the Content-Transfer-Encoding
1914 switch (ct->c_encoding) {
1916 /* Nothing to output */
1920 if (ct->c_type == CT_MESSAGE)
1921 adios(NULL, "internal error, invalid encoding");
1923 np = getcpy(ENCODING_FIELD);
1924 vp = concat(" ", "8bit", "\n", NULL);
1925 add_header(ct, np, vp);
1929 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1930 adios(NULL, "internal error, invalid encoding");
1932 np = getcpy(ENCODING_FIELD);
1933 vp = concat(" ", "quoted-printable", "\n", NULL);
1934 add_header(ct, np, vp);
1938 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1939 adios(NULL, "internal error, invalid encoding");
1941 np = getcpy(ENCODING_FIELD);
1942 vp = concat(" ", "base64", "\n", NULL);
1943 add_header(ct, np, vp);
1947 if (ct->c_type == CT_MESSAGE)
1948 adios(NULL, "internal error, invalid encoding");
1950 np = getcpy(ENCODING_FIELD);
1951 vp = concat(" ", "binary", "\n", NULL);
1952 add_header(ct, np, vp);
1956 adios(NULL, "unknown transfer encoding in content");
1961 ** Additional content specific header processing
1963 switch (ct->c_type) {
1966 struct multipart *m;
1969 m = (struct multipart *) ct->c_ctparams;
1970 for (part = m->mp_parts; part; part = part->mp_next) {
1980 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1983 e = (struct exbody *) ct->c_ctparams;
1984 build_headers(e->eb_content);
1997 static char nib2b64[0x40+1] =
1998 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2001 calculate_digest(CT ct, int asciiP)
2004 char buffer[BUFSIZ], *vp, *op;
2006 unsigned char digest[16];
2007 unsigned char outbuf[25];
2010 CE ce = ct->c_cefile;
2011 char *infilename = ce->ce_file ? ce->ce_file : ct->c_file;
2014 if ((in = fopen(infilename, "r")) == NULL)
2015 adios (infilename, "unable to open for reading");
2017 /* Initialize md5 context */
2018 MD5Init(&mdContext);
2020 /* calculate md5 message digest */
2022 while (fgets(buffer, sizeof(buffer) - 1, in)) {
2025 cp = buffer + strlen(buffer) - 1;
2026 if ((c = *cp) == '\n')
2029 MD5Update(&mdContext, (unsigned char *) buffer,
2030 (unsigned int) strlen(buffer));
2033 MD5Update(&mdContext, (unsigned char *) "\r\n",
2037 while ((cc = fread(buffer, sizeof(*buffer), sizeof(buffer),
2039 MD5Update(&mdContext, (unsigned char *) buffer,
2043 /* md5 finalization. Write digest and zero md5 context */
2044 MD5Final(digest, &mdContext);
2049 /* print debugging info */
2053 fprintf(stderr, "MD5 digest=");
2054 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
2056 fprintf(stderr, "%02x", *dp & 0xff);
2057 fprintf(stderr, "\n");
2060 /* encode the digest using base64 */
2061 for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
2062 cc > 0; cc -= 3, op += 4) {
2066 bits = (*dp++ & 0xff) << 16;
2068 bits |= (*dp++ & 0xff) << 8;
2070 bits |= *dp++ & 0xff;
2073 for (bp = op + 4; bp > op; bits >>= 6)
2074 *--bp = nib2b64[bits & 0x3f];
2082 /* null terminate string */
2085 /* now make copy and return string */
2086 vp = concat(" ", outbuf, "\n", NULL);