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 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>
43 static struct swit switches[] = {
51 { "noebcdicsafe", 0 },
67 { "norfc934mode", 0 },
73 { "rcache policy", 0 },
75 { "wcache policy", 0 },
76 #define CONTENTIDSW 16
78 #define NCONTENTIDSW 17
91 ** Directory to place tmp files. This must
92 ** be set before these routines are called.
98 static char prefix[] = "----- =_aaaaaaaaaa";
106 int make_intermediates(char *);
107 void content_error(char *, CT, char *, ...);
110 int find_cache(CT, int, int *, char *, char *, int);
113 int ftp_get(char *, char *, char *, char *, char *, char *, int, int);
116 void free_content(CT);
117 void free_ctinfo(CT);
118 void free_encoding(CT, int);
123 static int init_decoded_content(CT);
124 static char *fgetstr(char *, int, FILE *);
125 static int user_content(FILE *, char *, char *, CT *);
126 static void set_id(CT, int);
127 static int compose_content(CT);
128 static int scan_content(CT);
129 static int build_headers(CT);
130 static char *calculate_digest(CT, int);
131 static CT build_mime(char *);
138 extern char *cache_public;
139 extern char *cache_private;
152 static char infile[BUFSIZ];
153 static int unlink_infile = 0;
155 static char outfile[BUFSIZ];
156 static int unlink_outfile = 0;
158 static void unlink_done(int) NORETURN;
161 int output_message(CT, char *);
162 int output_message_fp(CT, FILE *, char*);
165 int list_all_messages(CT *, int, int, int, int);
168 void set_endian(void);
171 void free_content(CT);
175 main(int argc, char **argv)
177 int sizesw = 1, headsw = 1;
179 char *cp, buf[BUFSIZ];
180 char buffer[BUFSIZ], *compfile = NULL;
181 char **argp, **arguments;
189 setlocale(LC_ALL, "");
191 invo_name = mhbasename(argv[0]);
193 /* read user profile/context */
196 arguments = getarguments(invo_name, argc, argv, 1);
199 while ((cp = *argp++)) {
200 if (cp[0] == '-' && cp[1] == '\0') {
202 adios(NULL, "cannot specify both standard input and a file");
205 listsw = 0; /* turn off -list if using std in/out */
206 verbosw = 0; /* turn off -verbose listings */
210 switch (smatch(++cp, switches)) {
212 ambigsw(cp, switches);
215 adios(NULL, "-%s unknown", cp);
218 snprintf(buf, sizeof(buf), "%s [switches] file", invo_name);
219 print_help(buf, switches, 1);
222 print_version(invo_name);
226 icachesw = &rcachesw;
229 icachesw = &wcachesw;
231 if (!(cp = *argp++) || *cp == '-')
232 adios(NULL, "missing argument to %s",
234 switch (*icachesw = smatch(cp, caches)) {
239 adios(NULL, "%s unknown", cp);
306 adios(NULL, "only one composition file allowed");
313 if ((cp = getenv("MM_NOASK")) && strcmp(cp, "1")==0)
317 ** Check if we've specified an additional profile
319 if ((cp = getenv("MHBUILD"))) {
320 if ((fp = fopen(cp, "r"))) {
321 readconfig((struct node **) 0, fp, cp, 0);
324 admonish("", "unable to read $MHBUILD profile (%s)",
330 ** Read the standard profile setup
332 if ((fp = fopen(cp = etcpath("mhn.defaults"), "r"))) {
333 readconfig((struct node **) 0, fp, cp, 0);
337 /* Check for public cache location */
338 if ((cache_public = context_find(nmhcache)) && *cache_public != '/')
341 /* Check for private cache location */
342 if (!(cache_private = context_find(nmhprivcache)))
343 cache_private = ".cache";
344 cache_private = getcpy(toabsdir(cache_private));
347 ** Check for storage directory. If defined, we
348 ** will store temporary files there. Else we
349 ** store them in standard nmh directory.
351 if ((cp = context_find(nmhstorage)) && *cp)
352 tmp = concat(cp, "/", invo_name, NULL);
354 tmp = getcpy(toabsdir(invo_name));
356 /* Check if we have a file to process */
358 adios(NULL, "need to specify a %s composition file",
362 ** Process the composition file from standard input.
364 if (compfile[0] == '-' && compfile[1] == '\0') {
365 /* copy standard input to temporary file */
366 strncpy(infile, m_mktemp(invo_name, NULL, &fp),
368 while (fgets(buffer, BUFSIZ, stdin))
373 /* build the content structures for MIME message */
374 ct = build_mime(infile);
378 /* output MIME message to this temporary file */
379 strncpy(outfile, m_mktemp(invo_name, NULL, &fp_out),
383 /* output the message */
384 output_message_fp(ct, fp_out, outfile);
387 /* output the temp file to standard output */
388 if ((fp = fopen(outfile, "r")) == NULL)
389 adios(outfile, "unable to open");
390 while (fgets(buffer, BUFSIZ, fp))
391 fputs(buffer, stdout);
405 ** Process the composition file from a file.
408 /* build the content structures for MIME message */
409 ct = build_mime(compfile);
413 /* output MIME message to this temporary file */
414 strncpy(outfile, m_mktemp2(compfile, invo_name, NULL, &fp_out),
418 /* output the message */
419 output_message_fp(ct, fp_out, outfile);
423 ** List the message info
426 list_all_messages(cts, headsw, sizesw, verbosw, debugsw);
428 /* Rename composition draft */
429 snprintf(buffer, sizeof(buffer), "%s.orig", m_backup(compfile));
430 if (rename(compfile, buffer) == NOTOK) {
431 adios(compfile, "unable to rename comp draft %s to", buffer);
434 /* Rename output file to take its place */
435 if (rename(outfile, compfile) == NOTOK) {
436 advise(outfile, "unable to rename output %s to", compfile);
437 rename(buffer, compfile);
449 unlink_done(int status)
452 ** Check if we need to remove stray temporary files.
463 ** Main routine for translating composition file
464 ** into valid MIME message. It translates the draft
465 ** into a content structure (actually a tree of content
466 ** structures). This message then can be manipulated
467 ** in various ways, including being output via
471 build_mime(char *infile)
474 char buf[BUFSIZ], name[NAMESZ];
483 /* open the composition draft */
484 if ((in = fopen(infile, "r")) == NULL)
485 adios(infile, "unable to open for reading");
488 ** Allocate space for primary (outside) content
490 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
491 adios(NULL, "out of memory");
494 ** Allocate structure for handling decoded content
495 ** for this part. We don't really need this, but
496 ** allocate it to remain consistent.
498 init_decoded_content(ct);
501 ** Parse some of the header fields in the composition
502 ** draft into the linked list of header fields for
503 ** the new MIME message.
505 for (compnum = 1, state = FLD;;) {
506 switch (state = m_getfld(state, name, buf, sizeof(buf), in)) {
512 /* abort if draft has Mime-Version header field */
513 if (!mh_strcasecmp(name, VRSN_FIELD))
514 adios(NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
517 ** abort if draft has Content-Transfer-Encoding
520 if (!mh_strcasecmp(name, ENCODING_FIELD))
521 adios(NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
523 /* ignore any Content-Type fields in the header */
524 if (!mh_strcasecmp(name, TYPE_FIELD)) {
525 while (state == FLDPLUS)
526 state = m_getfld(state, name, buf,
531 /* get copies of the buffers */
535 /* if necessary, get rest of field */
536 while (state == FLDPLUS) {
537 state = m_getfld(state, name, buf,
539 vp = add(buf, vp); /* add to prev value */
542 /* Now add the header data to the list */
543 add_header(ct, np, vp);
546 /* if this wasn't the last hdr field, then continue */
552 adios(NULL, "draft has empty body -- no directives!");
557 fseek(in, (long) (-strlen(buf)), SEEK_CUR);
562 adios(NULL, "message format error in component #%d",
566 adios(NULL, "getfld() returned %d", state);
572 ** Now add the MIME-Version header field
573 ** to the list of header fields.
575 np = getcpy(VRSN_FIELD);
576 vp = concat(" ", VRSN_VALUE, "\n", NULL);
577 add_header(ct, np, vp);
580 ** We initally assume we will find multiple contents in the
581 ** draft. So create a multipart/mixed content to hold everything.
582 ** We can remove this later, if it is not needed.
584 if (get_ctinfo("multipart/mixed", ct, 0) == NOTOK)
586 ct->c_type = CT_MULTIPART;
587 ct->c_subtype = MULTI_MIXED;
588 ct->c_file = getcpy(infile);
590 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
591 adios(NULL, "out of memory");
592 ct->c_ctparams = (void *) m;
596 ** read and parse the composition file
597 ** and the directives it contains.
599 while (fgetstr(buf, sizeof(buf) - 1, in)) {
603 if (user_content(in, infile, buf, &p) == DONE) {
604 admonish(NULL, "ignoring spurious #end");
610 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
611 adios(NULL, "out of memory");
618 ** close the composition draft since
619 ** it's not needed any longer.
623 /* check if any contents were found */
625 adios(NULL, "no content directives found");
628 ** If only one content was found, then remove and
629 ** free the outer multipart content.
631 if (!m->mp_parts->mp_next) {
634 p = m->mp_parts->mp_part;
635 m->mp_parts->mp_part = NULL;
637 /* move header fields */
638 p->c_first_hf = ct->c_first_hf;
639 p->c_last_hf = ct->c_last_hf;
640 ct->c_first_hf = NULL;
641 ct->c_last_hf = NULL;
650 ** Fill out, or expand directives. Parse and execute
651 ** commands specified by profile composition strings.
655 if ((cp = strchr(prefix, 'a')) == NULL)
656 adios(NULL, "internal error(4)");
659 ** Scan the contents. Choose a transfer encoding, and
660 ** check if prefix for multipart boundary clashes with
661 ** any of the contents.
663 while (scan_content(ct) == NOTOK) {
668 adios(NULL, "giving up trying to find a unique delimiter string");
674 /* Build the rest of the header field structures */
682 ** Set up structures for placing unencoded
683 ** content when building parts.
687 init_decoded_content(CT ct)
691 if ((ce = (CE) calloc(1, sizeof(*ce))) == NULL)
692 adios(NULL, "out of memory");
695 ct->c_ceopenfnx = open7Bit; /* since unencoded */
696 ct->c_ceclosefnx = close_encoding;
697 ct->c_cesizefnx = NULL; /* since unencoded */
704 fgetstr(char *s, int n, FILE *stream)
708 for (ep = (cp = s) + n; cp < ep; ) {
711 if (!fgets(cp, n, stream))
712 return (cp != s ? s : NULL);
713 if (cp == s && *cp != '#')
716 cp += (i = strlen(cp)) - 1;
717 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
728 ** Parse the composition draft for text and directives.
729 ** Do initial setup of Content structure.
733 user_content(FILE *in, char *file, char *buf, CT *ctp)
742 struct str2init *s2i;
747 if (buf[0] == '\n' || strcmp(buf, "#\n") == 0) {
752 /* allocate basic Content structure */
753 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
754 adios(NULL, "out of memory");
757 /* allocate basic structure for handling decoded content */
758 init_decoded_content(ct);
765 ** Handle inline text. Check if line
766 ** is one of the following forms:
768 ** 1) doesn't begin with '#' (implicit directive)
769 ** 2) begins with "##" (implicit directive)
770 ** 3) begins with "#<"
772 if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
776 char content[BUFSIZ];
780 cp = m_mktemp2(NULL, invo_name, NULL, &out);
782 adios("mhbuild", "unable to create temporary file");
784 /* use a temp file to collect the plain text lines */
785 ce->ce_file = getcpy(cp);
788 if (buf[0] == '#' && buf[1] == '<') {
789 strncpy(content, buf + 2, sizeof(content));
796 /* the directive is implicit */
797 strncpy(content, "text/plain", sizeof(content));
799 strncpy(buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
803 if (headers >= 0 && uprf(buffer, DESCR_FIELD) &&
804 buffer[i=strlen(DESCR_FIELD)] == ':') {
808 ct->c_descr = add(buffer + i + 1, ct->c_descr);
809 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
810 adios(NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
818 adios(NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
826 if (headers >= 0 && uprf(buffer, DISPO_FIELD)
827 && buffer[i = strlen(DISPO_FIELD)] == ':') {
831 ct->c_dispo = add(buffer + i + 1, ct->c_dispo);
832 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
833 adios(NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
841 adios(NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
849 if (headers != 1 || buffer[0] != '\n')
855 if ((cp = fgetstr(buffer, sizeof(buffer) - 1, in))
858 if (buffer[0] == '#') {
861 if (buffer[1] != '#')
863 for (cp = (bp = buffer) + 1; *cp; cp++)
870 ct->c_end = ftell(out);
873 /* parse content type */
874 if (get_ctinfo(content, ct, inlineD) == NOTOK)
877 for (s2i = str2cts; s2i->si_key; s2i++)
878 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
880 if (!s2i->si_key && !uprf(ci->ci_type, "X-"))
884 ** check type specified (possibly implicitly)
886 switch (ct->c_type = s2i->si_val) {
888 if (!mh_strcasecmp(ci->ci_subtype, "rfc822")) {
889 ct->c_encoding = CE_7BIT;
894 adios(NULL, "it doesn't make sense to define an in-line %s content",
895 ct->c_type == CT_MESSAGE ? "message" :
901 if ((ct->c_ctinitfnx = s2i->si_init))
902 (*ct->c_ctinitfnx) (ct);
907 fseek(in, pos, SEEK_SET);
912 ** If we've reached this point, the next line
913 ** must be some type of explicit directive.
916 /* check if directive is external-type */
917 extrnal = (buf[1] == '@');
919 /* parse directive */
920 if (get_ctinfo(buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
923 /* check directive against the list of MIME types */
924 for (s2i = str2cts; s2i->si_key; s2i++)
925 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
929 ** Check if the directive specified a valid type.
930 ** This will happen if it was one of the following forms:
932 ** #type/subtype (or)
937 adios(NULL, "missing subtype in \"#%s\"", ci->ci_type);
939 switch (ct->c_type = s2i->si_val) {
941 adios(NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
945 if (!mh_strcasecmp(ci->ci_subtype, "partial"))
946 adios(NULL, "sorry, \"#%s/%s\" isn't supported", ci->ci_type, ci->ci_subtype);
947 if (!mh_strcasecmp(ci->ci_subtype, "external-body"))
948 adios(NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
950 adios(NULL, "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
954 if ((ct->c_ctinitfnx = s2i->si_init))
955 (*ct->c_ctinitfnx) (ct);
960 ** #@type/subtype (external types directive)
967 adios(NULL, "need external information for \"#@%s/%s\"", ci->ci_type, ci->ci_subtype);
970 snprintf(buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
975 ** Since we are using the current Content structure to
976 ** hold information about the type of the external
977 ** reference, we need to create another Content
978 ** structure for the message/external-body to wrap
981 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
982 adios(NULL, "out of memory");
985 if (get_ctinfo(buffer, ct, 0) == NOTOK)
987 ct->c_type = CT_MESSAGE;
988 ct->c_subtype = MESSAGE_EXTERNAL;
990 if ((e = (struct exbody *)
991 calloc(1, sizeof(*e))) == NULL)
992 adios(NULL, "out of memory");
993 ct->c_ctparams = (void *) e;
999 if (params_external(ct, 1) == NOTOK)
1005 /* Handle [file] argument */
1007 /* check if specifies command to execute */
1008 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
1009 for (cp = ci->ci_magic + 1; isspace(*cp); cp++)
1012 adios(NULL, "empty pipe command for #%s directive", ci->ci_type);
1017 /* record filename of decoded contents */
1018 ce->ce_file = ci->ci_magic;
1019 if (access(ce->ce_file, R_OK) == NOTOK)
1020 adios("reading", "unable to access %s for", ce->ce_file);
1021 if (listsw && stat(ce->ce_file, &st) != NOTOK)
1022 ct->c_end = (long) st.st_size;
1023 ci->ci_magic = NULL;
1029 ** No [file] argument, so check profile for
1030 ** method to compose content.
1032 snprintf(buffer, sizeof(buffer), "%s-compose-%s/%s",
1033 invo_name, ci->ci_type, ci->ci_subtype);
1034 if ((cp = context_find(buffer)) == NULL || *cp == '\0') {
1035 snprintf(buffer, sizeof(buffer), "%s-compose-%s",
1036 invo_name, ci->ci_type);
1037 if ((cp = context_find(buffer)) == NULL ||
1039 content_error(NULL, ct, "don't know how to compose content");
1043 ci->ci_magic = getcpy(cp);
1048 adios(NULL, "external definition not allowed for \"#%s\"",
1052 ** Message directive
1053 ** #forw [+folder] [msgs]
1055 if (!mh_strcasecmp(ci->ci_type, "forw")) {
1057 char *folder, *arguments[MAXARGS];
1063 ap = brkstring(ci->ci_magic, " ", "\n");
1064 for (i=0; ap[i] && i<MAXARGS-1; i++) {
1065 arguments[i] = ap[i];
1067 arguments[i] = NULL;
1070 arguments[0] = seq_cur;
1071 arguments[1] = NULL;
1075 /* search the arguments for a folder name */
1076 for (ap = arguments; *ap; ap++) {
1078 if (*cp == '+' || *cp == '@') {
1080 adios(NULL, "only one folder per #forw directive");
1082 folder = getcpy(expandfol(cp));
1086 /* else, use the current folder */
1088 folder = getcpy(getcurfol());
1090 if (!(mp = folder_read(folder)))
1091 adios(NULL, "unable to read folder %s", folder);
1092 for (ap = arguments; *ap; ap++) {
1094 if (*cp != '+' && *cp != '@')
1095 if (!m_convert(mp, cp))
1102 ** If there is more than one message to include, make this
1103 ** a content of type "multipart/digest" and insert each message
1104 ** as a subpart. If there is only one message, then make this
1105 ** a content of type "message/rfc822".
1107 if (mp->numsel > 1) {
1108 /* we are forwarding multiple messages */
1109 if (get_ctinfo("multipart/digest", ct, 0) == NOTOK)
1111 ct->c_type = CT_MULTIPART;
1112 ct->c_subtype = MULTI_DIGEST;
1114 if ((m = (struct multipart *)
1115 calloc(1, sizeof(*m))) == NULL)
1116 adios(NULL, "out of memory");
1117 ct->c_ctparams = (void *) m;
1120 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
1121 if (is_selected(mp, msgnum)) {
1126 if ((p = (CT) calloc(1, sizeof(*p)))
1128 adios(NULL, "out of memory");
1129 init_decoded_content(p);
1131 if (get_ctinfo("message/rfc822", p, 0)
1134 p->c_type = CT_MESSAGE;
1135 p->c_subtype = MESSAGE_RFC822;
1137 snprintf(buffer, sizeof(buffer),
1138 "%s/%d", mp->foldpath,
1140 pe->ce_file = getcpy(buffer);
1141 if (listsw && stat(pe->ce_file, &st)
1143 p->c_end = (long) st.st_size;
1145 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
1146 adios(NULL, "out of memory");
1148 pp = &part->mp_next;
1153 /* we are forwarding one message */
1154 if (get_ctinfo("message/rfc822", ct, 0) == NOTOK)
1156 ct->c_type = CT_MESSAGE;
1157 ct->c_subtype = MESSAGE_RFC822;
1159 msgnum = mp->lowsel;
1160 snprintf(buffer, sizeof(buffer), "%s/%d",
1161 mp->foldpath, msgnum);
1162 ce->ce_file = getcpy(buffer);
1163 if (listsw && stat(ce->ce_file, &st) != NOTOK)
1164 ct->c_end = (long) st.st_size;
1167 folder_free(mp); /* free folder/message structure */
1174 if (!mh_strcasecmp(ci->ci_type, "end")) {
1181 ** #begin [ alternative | parallel ]
1183 if (!mh_strcasecmp(ci->ci_type, "begin")) {
1184 if (!ci->ci_magic) {
1186 cp = SubMultiPart[vrsn - 1].kv_key;
1187 } else if (!mh_strcasecmp(ci->ci_magic, "alternative")) {
1188 vrsn = MULTI_ALTERNATE;
1189 cp = SubMultiPart[vrsn - 1].kv_key;
1190 } else if (!mh_strcasecmp(ci->ci_magic, "parallel")) {
1191 vrsn = MULTI_PARALLEL;
1192 cp = SubMultiPart[vrsn - 1].kv_key;
1193 } else if (uprf(ci->ci_magic, "digest")) {
1196 vrsn = MULTI_UNKNOWN;
1201 snprintf(buffer, sizeof(buffer), "multipart/%s", cp);
1202 if (get_ctinfo(buffer, ct, 0) == NOTOK)
1204 ct->c_type = CT_MULTIPART;
1205 ct->c_subtype = vrsn;
1207 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
1208 adios(NULL, "out of memory");
1209 ct->c_ctparams = (void *) m;
1212 while (fgetstr(buffer, sizeof(buffer) - 1, in)) {
1216 if (user_content(in, file, buffer, &p) == DONE) {
1218 adios(NULL, "empty \"#begin ... #end\" sequence");
1224 if ((part = (struct part *)
1225 calloc(1, sizeof(*part))) == NULL)
1226 adios(NULL, "out of memory");
1228 pp = &part->mp_next;
1231 admonish(NULL, "premature end-of-file, missing #end");
1236 ** Unknown directive
1238 adios(NULL, "unknown directive \"#%s\"", ci->ci_type);
1239 return NOTOK; /* NOT REACHED */
1244 set_id(CT ct, int top)
1248 static time_t clock = 0;
1249 static char *msgfmt;
1253 snprintf(msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
1254 (int) getpid(), (long) clock, LocalName());
1256 msgfmt = getcpy(msgid);
1258 snprintf(msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
1259 ct->c_id = getcpy(msgid);
1263 static char ebcdicsafe[0x100] = {
1264 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1265 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1266 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1267 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1268 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
1269 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1270 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1271 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1272 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1273 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1274 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1275 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
1276 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1277 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1278 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1279 0x01, 0x01, 0x01, 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,
1286 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1287 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1288 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1289 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1290 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1291 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1292 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1293 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1294 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1295 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1300 ** Fill out, or expand the various contents in the composition
1301 ** draft. Read-in any necessary files. Parse and execute any
1302 ** commands specified by profile composition strings.
1306 compose_content(CT ct)
1308 CE ce = ct->c_cefile;
1310 switch (ct->c_type) {
1315 char partnam[BUFSIZ];
1316 struct multipart *m = (struct multipart *) ct->c_ctparams;
1320 snprintf(partnam, sizeof(partnam), "%s.",
1322 pp = partnam + strlen(partnam);
1327 /* first, we call compose_content on all the subparts */
1328 for (part = m->mp_parts, partnum = 1; part;
1329 part = part->mp_next, partnum++) {
1330 CT p = part->mp_part;
1332 sprintf(pp, "%d", partnum);
1333 p->c_partno = getcpy(partnam);
1334 if (compose_content(p) == NOTOK)
1339 ** If the -rfc934mode switch is given, then check all
1340 ** the subparts of a multipart/digest. If they are all
1341 ** message/rfc822, then mark this content and all
1342 ** subparts with the rfc934 compatibility mode flag.
1344 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
1347 for (part = m->mp_parts; part; part = part->mp_next) {
1348 CT p = part->mp_part;
1350 if (p->c_subtype != MESSAGE_RFC822) {
1355 ct->c_rfc934 = is934;
1356 for (part = m->mp_parts; part; part = part->mp_next) {
1357 CT p = part->mp_part;
1359 if ((p->c_rfc934 = is934))
1365 ct->c_end = (partnum = strlen(prefix) + 2) + 2;
1369 for (part = m->mp_parts; part; part = part->mp_next)
1370 ct->c_end += part->mp_part->c_end + partnum;
1376 /* Nothing to do for type message */
1380 ** Discrete types (text/application/audio/image/video)
1385 int xstdout, len, buflen;
1386 char *bp, **ap, *cp;
1387 char *vec[4], buffer[BUFSIZ];
1389 CI ci = &ct->c_ctinfo;
1392 if (!(cp = ci->ci_magic))
1393 adios(NULL, "internal error(5)");
1395 tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1396 if (tfile == NULL) {
1397 adios("mhbuild", "unable to create temporary file");
1399 ce->ce_file = getcpy(tfile);
1404 /* Get buffer ready to go */
1407 buflen = sizeof(buffer);
1410 ** Parse composition string into buffer
1412 for ( ; *cp; cp++) {
1418 ** insert parameters from
1424 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1425 snprintf(bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1435 /* %f, and stdout is not-redirected */
1441 ** insert temporary filename
1442 ** where content should be
1445 snprintf(bp, buflen, "%s", ce->ce_file);
1449 /* insert content subtype */
1450 strncpy(bp, ci->ci_subtype, buflen);
1454 /* insert character % */
1475 printf("composing content %s/%s from command\n\t%s\n", ci->ci_type, ci->ci_subtype, buffer);
1477 fflush(stdout); /* not sure if need for -noverbose */
1484 if ((out = fopen(ce->ce_file, "w")) == NULL)
1485 adios(ce->ce_file, "unable to open for writing");
1487 switch (child_id = fork()) {
1489 adios("fork", "unable to fork");
1494 dup2(fileno(out), 1);
1496 execvp("/bin/sh", vec);
1497 fprintf(stderr, "unable to exec ");
1504 if (pidXwait(child_id, NULL))
1510 /* Check size of file */
1511 if (listsw && ct->c_end == 0L) {
1514 if (stat(ce->ce_file, &st) != NOTOK)
1515 ct->c_end = (long) st.st_size;
1525 ** Scan the content.
1527 ** 1) choose a transfer encoding.
1528 ** 2) check for clashes with multipart boundary string.
1529 ** 3) for text content, figure out which character set is being used.
1531 ** If there is a clash with one of the contents and the multipart boundary,
1532 ** this function will exit with NOTOK. This will cause the scanning process
1533 ** to be repeated with a different multipart boundary. It is possible
1534 ** (although highly unlikely) that this scan will be repeated multiple times.
1541 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1542 int checklinelen = 0, linelen = 0; /* check for long lines */
1543 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1544 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1545 int checkebcdic = 0, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
1546 unsigned char *cp = NULL, buffer[BUFSIZ];
1547 struct text *t = NULL;
1549 CE ce = ct->c_cefile;
1552 ** handle multipart by scanning all subparts
1553 ** and then checking their encoding.
1555 if (ct->c_type == CT_MULTIPART) {
1556 struct multipart *m = (struct multipart *) ct->c_ctparams;
1559 /* initially mark the domain of enclosing multipart as 7bit */
1560 ct->c_encoding = CE_7BIT;
1562 for (part = m->mp_parts; part; part = part->mp_next) {
1563 CT p = part->mp_part;
1565 if (scan_content(p) == NOTOK) {
1566 /* choose encoding for subpart */
1571 ** if necessary, enlarge encoding for enclosing
1574 if (p->c_encoding == CE_BINARY)
1575 ct->c_encoding = CE_BINARY;
1576 if (p->c_encoding == CE_8BIT &&
1577 ct->c_encoding != CE_BINARY)
1578 ct->c_encoding = CE_8BIT;
1585 ** Decide what to check while scanning this content.
1587 switch (ct->c_type) {
1591 if (ct->c_subtype == TEXT_PLAIN) {
1596 checkebcdic = ebcdicsw;
1602 case CT_APPLICATION:
1604 checkebcdic = ebcdicsw;
1616 /* don't check anything for message/external */
1617 if (ct->c_subtype == MESSAGE_EXTERNAL)
1627 ** Don't check anything for these types,
1628 ** since we are forcing use of base64.
1639 ** Scan the unencoded content
1641 if (check8bit || checklinelen || checklinespace || checkboundary) {
1642 if ((in = fopen(ce->ce_file, "r")) == NULL)
1643 adios(ce->ce_file, "unable to open for reading");
1644 len = strlen(prefix);
1646 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1648 ** Check for 8bit data.
1651 for (cp = buffer; *cp; cp++) {
1652 if (!isascii(*cp)) {
1654 /* no need to keep checking */
1658 ** Check if character is ebcdic-safe.
1659 ** We only check this if also checking
1662 if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1664 /* no need to keep checking */
1671 ** Check line length.
1673 if (checklinelen && (strlen(buffer) > CPERLIN + 1)) {
1675 checklinelen = 0; /* no need to keep checking */
1679 ** Check if line ends with a space.
1681 if (checklinespace &&
1682 (cp = buffer + strlen(buffer) - 2) >
1683 buffer && isspace(*cp)) {
1685 /* no need to keep checking */
1690 ** Check if content contains a line that clashes
1691 ** with our standard boundary for multipart messages.
1693 if (checkboundary && buffer[0] == '-' &&
1695 for (cp = buffer + strlen(buffer) - 1;
1700 if (strncmp(buffer + 2, prefix, len)==0 &&
1701 isdigit(buffer[2 + len])) {
1703 /* no need to keep checking */
1712 ** Decide which transfer encoding to use.
1714 switch (ct->c_type) {
1717 ** If the text content didn't specify a character
1718 ** set, we need to figure out which one was used.
1720 t = (struct text *) ct->c_ctparams;
1721 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1722 CI ci = &ct->c_ctinfo;
1725 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1729 t->tx_charset = CHARSET_UNKNOWN;
1730 *ap = concat("charset=", write_charset_8bit(),
1733 t->tx_charset = CHARSET_USASCII;
1734 *ap = getcpy("charset=us-ascii");
1737 cp = strchr(*ap++, '=');
1743 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1745 ct->c_encoding = CE_QUOTED;
1747 ct->c_encoding = CE_7BIT;
1750 case CT_APPLICATION:
1751 /* For application type, use base64, except when postscript */
1752 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1754 ct->c_encoding = (ct->c_subtype ==
1755 APPLICATION_POSTSCRIPT) ?
1756 CE_QUOTED : CE_BASE64;
1758 ct->c_encoding = CE_7BIT;
1762 ct->c_encoding = CE_7BIT;
1768 /* For audio, image, and video contents, just use base64 */
1769 ct->c_encoding = CE_BASE64;
1773 return (boundaryclash ? NOTOK : OK);
1778 ** Scan the content structures, and build header
1779 ** fields that will need to be output into the
1784 build_headers(CT ct)
1786 int cc, mailbody, len;
1788 char *np, *vp, buffer[BUFSIZ];
1789 CI ci = &ct->c_ctinfo;
1792 ** If message is type multipart, then add the multipart
1793 ** boundary to the list of attribute/value pairs.
1795 if (ct->c_type == CT_MULTIPART) {
1797 static int level = 0; /* store nesting level */
1801 snprintf(buffer, sizeof(buffer), "boundary=%s%d",
1803 cp = strchr(*ap++ = getcpy(buffer), '=');
1810 ** Skip the output of Content-Type, parameters, content
1811 ** description and disposition, and Content-ID if the
1812 ** content is of type "message" and the rfc934 compatibility
1813 ** flag is set (which means we are inside multipart/digest
1814 ** and the switch -rfc934mode was given).
1816 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1820 ** output the content type and subtype
1822 np = getcpy(TYPE_FIELD);
1823 vp = concat(" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1825 /* keep track of length of line */
1826 len = strlen(TYPE_FIELD) + strlen(ci->ci_type) +
1827 strlen(ci->ci_subtype) + 3;
1829 mailbody = ct->c_type == CT_MESSAGE &&
1830 ct->c_subtype == MESSAGE_EXTERNAL &&
1831 ((struct exbody *) ct->c_ctparams)->eb_body;
1834 ** Append the attribute/value pairs to
1835 ** the end of the Content-Type line.
1837 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1838 if (mailbody && !mh_strcasecmp(*ap, "body"))
1844 snprintf(buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1845 if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) {
1846 vp = add("\n\t", vp);
1852 vp = add(buffer, vp);
1857 ** Append any RFC-822 comment to the end of
1858 ** the Content-Type line.
1860 if (ci->ci_comment) {
1861 snprintf(buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1862 if (len + 1 + (cc = 2 + strlen(ci->ci_comment)) >= CPERLIN) {
1863 vp = add("\n\t", vp);
1869 vp = add(buffer, vp);
1873 add_header(ct, np, vp);
1876 ** output the Content-ID, unless disabled by -nocontentid
1878 if (contentidsw && ct->c_id) {
1879 np = getcpy(ID_FIELD);
1880 vp = concat(" ", ct->c_id, NULL);
1881 add_header(ct, np, vp);
1885 ** output the Content-Description
1888 np = getcpy(DESCR_FIELD);
1889 vp = concat(" ", ct->c_descr, NULL);
1890 add_header(ct, np, vp);
1894 ** output the Content-Disposition
1897 np = getcpy(DISPO_FIELD);
1898 vp = concat(" ", ct->c_dispo, NULL);
1899 add_header(ct, np, vp);
1904 ** If this is the internal content structure for a
1905 ** "message/external", then we are done with the
1906 ** headers (since it has no body).
1912 ** output the Content-MD5
1915 np = getcpy(MD5_FIELD);
1916 vp = calculate_digest(ct, (ct->c_encoding == CE_QUOTED) ?
1918 add_header(ct, np, vp);
1922 ** output the Content-Transfer-Encoding
1924 switch (ct->c_encoding) {
1926 /* Nothing to output */
1930 if (ct->c_type == CT_MESSAGE)
1931 adios(NULL, "internal error, invalid encoding");
1933 np = getcpy(ENCODING_FIELD);
1934 vp = concat(" ", "8bit", "\n", NULL);
1935 add_header(ct, np, vp);
1939 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1940 adios(NULL, "internal error, invalid encoding");
1942 np = getcpy(ENCODING_FIELD);
1943 vp = concat(" ", "quoted-printable", "\n", NULL);
1944 add_header(ct, np, vp);
1948 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1949 adios(NULL, "internal error, invalid encoding");
1951 np = getcpy(ENCODING_FIELD);
1952 vp = concat(" ", "base64", "\n", NULL);
1953 add_header(ct, np, vp);
1957 if (ct->c_type == CT_MESSAGE)
1958 adios(NULL, "internal error, invalid encoding");
1960 np = getcpy(ENCODING_FIELD);
1961 vp = concat(" ", "binary", "\n", NULL);
1962 add_header(ct, np, vp);
1966 adios(NULL, "unknown transfer encoding in content");
1971 ** Additional content specific header processing
1973 switch (ct->c_type) {
1976 struct multipart *m;
1979 m = (struct multipart *) ct->c_ctparams;
1980 for (part = m->mp_parts; part; part = part->mp_next) {
1990 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1993 e = (struct exbody *) ct->c_ctparams;
1994 build_headers(e->eb_content);
2007 static char nib2b64[0x40+1] =
2008 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2011 calculate_digest(CT ct, int asciiP)
2014 char buffer[BUFSIZ], *vp, *op;
2016 unsigned char digest[16];
2017 unsigned char outbuf[25];
2020 CE ce = ct->c_cefile;
2023 if ((in = fopen(ce->ce_file, "r")) == NULL)
2024 adios(ce->ce_file, "unable to open for reading");
2026 /* Initialize md5 context */
2027 MD5Init(&mdContext);
2029 /* calculate md5 message digest */
2031 while (fgets(buffer, sizeof(buffer) - 1, in)) {
2034 cp = buffer + strlen(buffer) - 1;
2035 if ((c = *cp) == '\n')
2038 MD5Update(&mdContext, (unsigned char *) buffer,
2039 (unsigned int) strlen(buffer));
2042 MD5Update(&mdContext, (unsigned char *) "\r\n",
2046 while ((cc = fread(buffer, sizeof(*buffer), sizeof(buffer),
2048 MD5Update(&mdContext, (unsigned char *) buffer,
2052 /* md5 finalization. Write digest and zero md5 context */
2053 MD5Final(digest, &mdContext);
2058 /* print debugging info */
2062 fprintf(stderr, "MD5 digest=");
2063 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
2065 fprintf(stderr, "%02x", *dp & 0xff);
2066 fprintf(stderr, "\n");
2069 /* encode the digest using base64 */
2070 for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
2071 cc > 0; cc -= 3, op += 4) {
2075 bits = (*dp++ & 0xff) << 16;
2077 bits |= (*dp++ & 0xff) << 8;
2079 bits |= *dp++ & 0xff;
2082 for (bp = op + 4; bp > op; bits >>= 6)
2083 *--bp = nib2b64[bits & 0x3f];
2091 /* null terminate string */
2094 /* now make copy and return string */
2095 vp = concat(" ", outbuf, "\n", NULL);