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];
1061 ap = brkstring(ci->ci_magic, " ", "\n");
1062 copyip(ap, arguments, MAXARGS);
1064 arguments[0] = seq_cur;
1065 arguments[1] = NULL;
1069 /* search the arguments for a folder name */
1070 for (ap = arguments; *ap; ap++) {
1072 if (*cp == '+' || *cp == '@') {
1074 adios(NULL, "only one folder per #forw directive");
1076 folder = getcpy(expandfol(cp));
1080 /* else, use the current folder */
1082 folder = getcpy(getcurfol());
1084 if (!(mp = folder_read(folder)))
1085 adios(NULL, "unable to read folder %s", folder);
1086 for (ap = arguments; *ap; ap++) {
1088 if (*cp != '+' && *cp != '@')
1089 if (!m_convert(mp, cp))
1096 ** If there is more than one message to include, make this
1097 ** a content of type "multipart/digest" and insert each message
1098 ** as a subpart. If there is only one message, then make this
1099 ** a content of type "message/rfc822".
1101 if (mp->numsel > 1) {
1102 /* we are forwarding multiple messages */
1103 if (get_ctinfo("multipart/digest", ct, 0) == NOTOK)
1105 ct->c_type = CT_MULTIPART;
1106 ct->c_subtype = MULTI_DIGEST;
1108 if ((m = (struct multipart *)
1109 calloc(1, sizeof(*m))) == NULL)
1110 adios(NULL, "out of memory");
1111 ct->c_ctparams = (void *) m;
1114 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
1115 if (is_selected(mp, msgnum)) {
1120 if ((p = (CT) calloc(1, sizeof(*p)))
1122 adios(NULL, "out of memory");
1123 init_decoded_content(p);
1125 if (get_ctinfo("message/rfc822", p, 0)
1128 p->c_type = CT_MESSAGE;
1129 p->c_subtype = MESSAGE_RFC822;
1131 snprintf(buffer, sizeof(buffer),
1132 "%s/%d", mp->foldpath,
1134 pe->ce_file = getcpy(buffer);
1135 if (listsw && stat(pe->ce_file, &st)
1137 p->c_end = (long) st.st_size;
1139 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
1140 adios(NULL, "out of memory");
1142 pp = &part->mp_next;
1147 /* we are forwarding one message */
1148 if (get_ctinfo("message/rfc822", ct, 0) == NOTOK)
1150 ct->c_type = CT_MESSAGE;
1151 ct->c_subtype = MESSAGE_RFC822;
1153 msgnum = mp->lowsel;
1154 snprintf(buffer, sizeof(buffer), "%s/%d",
1155 mp->foldpath, msgnum);
1156 ce->ce_file = getcpy(buffer);
1157 if (listsw && stat(ce->ce_file, &st) != NOTOK)
1158 ct->c_end = (long) st.st_size;
1161 folder_free(mp); /* free folder/message structure */
1168 if (!mh_strcasecmp(ci->ci_type, "end")) {
1175 ** #begin [ alternative | parallel ]
1177 if (!mh_strcasecmp(ci->ci_type, "begin")) {
1178 if (!ci->ci_magic) {
1180 cp = SubMultiPart[vrsn - 1].kv_key;
1181 } else if (!mh_strcasecmp(ci->ci_magic, "alternative")) {
1182 vrsn = MULTI_ALTERNATE;
1183 cp = SubMultiPart[vrsn - 1].kv_key;
1184 } else if (!mh_strcasecmp(ci->ci_magic, "parallel")) {
1185 vrsn = MULTI_PARALLEL;
1186 cp = SubMultiPart[vrsn - 1].kv_key;
1187 } else if (uprf(ci->ci_magic, "digest")) {
1190 vrsn = MULTI_UNKNOWN;
1195 snprintf(buffer, sizeof(buffer), "multipart/%s", cp);
1196 if (get_ctinfo(buffer, ct, 0) == NOTOK)
1198 ct->c_type = CT_MULTIPART;
1199 ct->c_subtype = vrsn;
1201 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
1202 adios(NULL, "out of memory");
1203 ct->c_ctparams = (void *) m;
1206 while (fgetstr(buffer, sizeof(buffer) - 1, in)) {
1210 if (user_content(in, file, buffer, &p) == DONE) {
1212 adios(NULL, "empty \"#begin ... #end\" sequence");
1218 if ((part = (struct part *)
1219 calloc(1, sizeof(*part))) == NULL)
1220 adios(NULL, "out of memory");
1222 pp = &part->mp_next;
1225 admonish(NULL, "premature end-of-file, missing #end");
1230 ** Unknown directive
1232 adios(NULL, "unknown directive \"#%s\"", ci->ci_type);
1233 return NOTOK; /* NOT REACHED */
1238 set_id(CT ct, int top)
1242 static time_t clock = 0;
1243 static char *msgfmt;
1247 snprintf(msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
1248 (int) getpid(), (long) clock, LocalName());
1250 msgfmt = getcpy(msgid);
1252 snprintf(msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
1253 ct->c_id = getcpy(msgid);
1257 static char ebcdicsafe[0x100] = {
1258 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1259 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1260 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1261 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1262 0x01, 0x00, 0x00, 0x00, 0x00, 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, 0x01, 0x01, 0x01, 0x01, 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, 0x01,
1270 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1271 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1272 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1273 0x01, 0x01, 0x01, 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,
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
1294 ** Fill out, or expand the various contents in the composition
1295 ** draft. Read-in any necessary files. Parse and execute any
1296 ** commands specified by profile composition strings.
1300 compose_content(CT ct)
1302 CE ce = ct->c_cefile;
1304 switch (ct->c_type) {
1309 char partnam[BUFSIZ];
1310 struct multipart *m = (struct multipart *) ct->c_ctparams;
1314 snprintf(partnam, sizeof(partnam), "%s.",
1316 pp = partnam + strlen(partnam);
1321 /* first, we call compose_content on all the subparts */
1322 for (part = m->mp_parts, partnum = 1; part;
1323 part = part->mp_next, partnum++) {
1324 CT p = part->mp_part;
1326 sprintf(pp, "%d", partnum);
1327 p->c_partno = getcpy(partnam);
1328 if (compose_content(p) == NOTOK)
1333 ** If the -rfc934mode switch is given, then check all
1334 ** the subparts of a multipart/digest. If they are all
1335 ** message/rfc822, then mark this content and all
1336 ** subparts with the rfc934 compatibility mode flag.
1338 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
1341 for (part = m->mp_parts; part; part = part->mp_next) {
1342 CT p = part->mp_part;
1344 if (p->c_subtype != MESSAGE_RFC822) {
1349 ct->c_rfc934 = is934;
1350 for (part = m->mp_parts; part; part = part->mp_next) {
1351 CT p = part->mp_part;
1353 if ((p->c_rfc934 = is934))
1359 ct->c_end = (partnum = strlen(prefix) + 2) + 2;
1363 for (part = m->mp_parts; part; part = part->mp_next)
1364 ct->c_end += part->mp_part->c_end + partnum;
1370 /* Nothing to do for type message */
1374 ** Discrete types (text/application/audio/image/video)
1379 int xstdout, len, buflen;
1380 char *bp, **ap, *cp;
1381 char *vec[4], buffer[BUFSIZ];
1383 CI ci = &ct->c_ctinfo;
1386 if (!(cp = ci->ci_magic))
1387 adios(NULL, "internal error(5)");
1389 tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1390 if (tfile == NULL) {
1391 adios("mhbuild", "unable to create temporary file");
1393 ce->ce_file = getcpy(tfile);
1398 /* Get buffer ready to go */
1401 buflen = sizeof(buffer);
1404 ** Parse composition string into buffer
1406 for ( ; *cp; cp++) {
1412 ** insert parameters from
1418 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1419 snprintf(bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1429 /* %f, and stdout is not-redirected */
1435 ** insert temporary filename
1436 ** where content should be
1439 snprintf(bp, buflen, "%s", ce->ce_file);
1443 /* insert content subtype */
1444 strncpy(bp, ci->ci_subtype, buflen);
1448 /* insert character % */
1469 printf("composing content %s/%s from command\n\t%s\n", ci->ci_type, ci->ci_subtype, buffer);
1471 fflush(stdout); /* not sure if need for -noverbose */
1478 if ((out = fopen(ce->ce_file, "w")) == NULL)
1479 adios(ce->ce_file, "unable to open for writing");
1481 switch (child_id = fork()) {
1483 adios("fork", "unable to fork");
1488 dup2(fileno(out), 1);
1490 execvp("/bin/sh", vec);
1491 fprintf(stderr, "unable to exec ");
1498 if (pidXwait(child_id, NULL))
1504 /* Check size of file */
1505 if (listsw && ct->c_end == 0L) {
1508 if (stat(ce->ce_file, &st) != NOTOK)
1509 ct->c_end = (long) st.st_size;
1519 ** Scan the content.
1521 ** 1) choose a transfer encoding.
1522 ** 2) check for clashes with multipart boundary string.
1523 ** 3) for text content, figure out which character set is being used.
1525 ** If there is a clash with one of the contents and the multipart boundary,
1526 ** this function will exit with NOTOK. This will cause the scanning process
1527 ** to be repeated with a different multipart boundary. It is possible
1528 ** (although highly unlikely) that this scan will be repeated multiple times.
1535 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1536 int checklinelen = 0, linelen = 0; /* check for long lines */
1537 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1538 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1539 int checkebcdic = 0, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
1540 unsigned char *cp = NULL, buffer[BUFSIZ];
1541 struct text *t = NULL;
1543 CE ce = ct->c_cefile;
1546 ** handle multipart by scanning all subparts
1547 ** and then checking their encoding.
1549 if (ct->c_type == CT_MULTIPART) {
1550 struct multipart *m = (struct multipart *) ct->c_ctparams;
1553 /* initially mark the domain of enclosing multipart as 7bit */
1554 ct->c_encoding = CE_7BIT;
1556 for (part = m->mp_parts; part; part = part->mp_next) {
1557 CT p = part->mp_part;
1559 if (scan_content(p) == NOTOK) {
1560 /* choose encoding for subpart */
1565 ** if necessary, enlarge encoding for enclosing
1568 if (p->c_encoding == CE_BINARY)
1569 ct->c_encoding = CE_BINARY;
1570 if (p->c_encoding == CE_8BIT &&
1571 ct->c_encoding != CE_BINARY)
1572 ct->c_encoding = CE_8BIT;
1579 ** Decide what to check while scanning this content.
1581 switch (ct->c_type) {
1585 if (ct->c_subtype == TEXT_PLAIN) {
1590 checkebcdic = ebcdicsw;
1596 case CT_APPLICATION:
1598 checkebcdic = ebcdicsw;
1610 /* don't check anything for message/external */
1611 if (ct->c_subtype == MESSAGE_EXTERNAL)
1621 ** Don't check anything for these types,
1622 ** since we are forcing use of base64.
1633 ** Scan the unencoded content
1635 if (check8bit || checklinelen || checklinespace || checkboundary) {
1636 if ((in = fopen(ce->ce_file, "r")) == NULL)
1637 adios(ce->ce_file, "unable to open for reading");
1638 len = strlen(prefix);
1640 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1642 ** Check for 8bit data.
1645 for (cp = buffer; *cp; cp++) {
1646 if (!isascii(*cp)) {
1648 /* no need to keep checking */
1652 ** Check if character is ebcdic-safe.
1653 ** We only check this if also checking
1656 if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1658 /* no need to keep checking */
1665 ** Check line length.
1667 if (checklinelen && (strlen(buffer) > CPERLIN + 1)) {
1669 checklinelen = 0; /* no need to keep checking */
1673 ** Check if line ends with a space.
1675 if (checklinespace &&
1676 (cp = buffer + strlen(buffer) - 2) >
1677 buffer && isspace(*cp)) {
1679 /* no need to keep checking */
1684 ** Check if content contains a line that clashes
1685 ** with our standard boundary for multipart messages.
1687 if (checkboundary && buffer[0] == '-' &&
1689 for (cp = buffer + strlen(buffer) - 1;
1694 if (strncmp(buffer + 2, prefix, len)==0 &&
1695 isdigit(buffer[2 + len])) {
1697 /* no need to keep checking */
1706 ** Decide which transfer encoding to use.
1708 switch (ct->c_type) {
1711 ** If the text content didn't specify a character
1712 ** set, we need to figure out which one was used.
1714 t = (struct text *) ct->c_ctparams;
1715 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1716 CI ci = &ct->c_ctinfo;
1719 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1723 t->tx_charset = CHARSET_UNKNOWN;
1724 *ap = concat("charset=", write_charset_8bit(),
1727 t->tx_charset = CHARSET_USASCII;
1728 *ap = getcpy("charset=us-ascii");
1731 cp = strchr(*ap++, '=');
1737 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1739 ct->c_encoding = CE_QUOTED;
1741 ct->c_encoding = CE_7BIT;
1744 case CT_APPLICATION:
1745 /* For application type, use base64, except when postscript */
1746 if (contains8bit || ebcdicunsafe || linelen || linespace ||
1748 ct->c_encoding = (ct->c_subtype ==
1749 APPLICATION_POSTSCRIPT) ?
1750 CE_QUOTED : CE_BASE64;
1752 ct->c_encoding = CE_7BIT;
1756 ct->c_encoding = CE_7BIT;
1762 /* For audio, image, and video contents, just use base64 */
1763 ct->c_encoding = CE_BASE64;
1767 return (boundaryclash ? NOTOK : OK);
1772 ** Scan the content structures, and build header
1773 ** fields that will need to be output into the
1778 build_headers(CT ct)
1780 int cc, mailbody, len;
1782 char *np, *vp, buffer[BUFSIZ];
1783 CI ci = &ct->c_ctinfo;
1786 ** If message is type multipart, then add the multipart
1787 ** boundary to the list of attribute/value pairs.
1789 if (ct->c_type == CT_MULTIPART) {
1791 static int level = 0; /* store nesting level */
1795 snprintf(buffer, sizeof(buffer), "boundary=%s%d",
1797 cp = strchr(*ap++ = getcpy(buffer), '=');
1804 ** Skip the output of Content-Type, parameters, content
1805 ** description and disposition, and Content-ID if the
1806 ** content is of type "message" and the rfc934 compatibility
1807 ** flag is set (which means we are inside multipart/digest
1808 ** and the switch -rfc934mode was given).
1810 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1814 ** output the content type and subtype
1816 np = getcpy(TYPE_FIELD);
1817 vp = concat(" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1819 /* keep track of length of line */
1820 len = strlen(TYPE_FIELD) + strlen(ci->ci_type) +
1821 strlen(ci->ci_subtype) + 3;
1823 mailbody = ct->c_type == CT_MESSAGE &&
1824 ct->c_subtype == MESSAGE_EXTERNAL &&
1825 ((struct exbody *) ct->c_ctparams)->eb_body;
1828 ** Append the attribute/value pairs to
1829 ** the end of the Content-Type line.
1831 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1832 if (mailbody && !mh_strcasecmp(*ap, "body"))
1838 snprintf(buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1839 if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) {
1840 vp = add("\n\t", vp);
1846 vp = add(buffer, vp);
1851 ** Append any RFC-822 comment to the end of
1852 ** the Content-Type line.
1854 if (ci->ci_comment) {
1855 snprintf(buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1856 if (len + 1 + (cc = 2 + strlen(ci->ci_comment)) >= CPERLIN) {
1857 vp = add("\n\t", vp);
1863 vp = add(buffer, vp);
1867 add_header(ct, np, vp);
1870 ** output the Content-ID, unless disabled by -nocontentid
1872 if (contentidsw && ct->c_id) {
1873 np = getcpy(ID_FIELD);
1874 vp = concat(" ", ct->c_id, NULL);
1875 add_header(ct, np, vp);
1879 ** output the Content-Description
1882 np = getcpy(DESCR_FIELD);
1883 vp = concat(" ", ct->c_descr, NULL);
1884 add_header(ct, np, vp);
1888 ** output the Content-Disposition
1891 np = getcpy(DISPO_FIELD);
1892 vp = concat(" ", ct->c_dispo, NULL);
1893 add_header(ct, np, vp);
1898 ** If this is the internal content structure for a
1899 ** "message/external", then we are done with the
1900 ** headers (since it has no body).
1906 ** output the Content-MD5
1909 np = getcpy(MD5_FIELD);
1910 vp = calculate_digest(ct, (ct->c_encoding == CE_QUOTED) ?
1912 add_header(ct, np, vp);
1916 ** output the Content-Transfer-Encoding
1918 switch (ct->c_encoding) {
1920 /* Nothing to output */
1924 if (ct->c_type == CT_MESSAGE)
1925 adios(NULL, "internal error, invalid encoding");
1927 np = getcpy(ENCODING_FIELD);
1928 vp = concat(" ", "8bit", "\n", NULL);
1929 add_header(ct, np, vp);
1933 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1934 adios(NULL, "internal error, invalid encoding");
1936 np = getcpy(ENCODING_FIELD);
1937 vp = concat(" ", "quoted-printable", "\n", NULL);
1938 add_header(ct, np, vp);
1942 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1943 adios(NULL, "internal error, invalid encoding");
1945 np = getcpy(ENCODING_FIELD);
1946 vp = concat(" ", "base64", "\n", NULL);
1947 add_header(ct, np, vp);
1951 if (ct->c_type == CT_MESSAGE)
1952 adios(NULL, "internal error, invalid encoding");
1954 np = getcpy(ENCODING_FIELD);
1955 vp = concat(" ", "binary", "\n", NULL);
1956 add_header(ct, np, vp);
1960 adios(NULL, "unknown transfer encoding in content");
1965 ** Additional content specific header processing
1967 switch (ct->c_type) {
1970 struct multipart *m;
1973 m = (struct multipart *) ct->c_ctparams;
1974 for (part = m->mp_parts; part; part = part->mp_next) {
1984 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1987 e = (struct exbody *) ct->c_ctparams;
1988 build_headers(e->eb_content);
2001 static char nib2b64[0x40+1] =
2002 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2005 calculate_digest(CT ct, int asciiP)
2008 char buffer[BUFSIZ], *vp, *op;
2010 unsigned char digest[16];
2011 unsigned char outbuf[25];
2014 CE ce = ct->c_cefile;
2017 if ((in = fopen(ce->ce_file, "r")) == NULL)
2018 adios(ce->ce_file, "unable to open for reading");
2020 /* Initialize md5 context */
2021 MD5Init(&mdContext);
2023 /* calculate md5 message digest */
2025 while (fgets(buffer, sizeof(buffer) - 1, in)) {
2028 cp = buffer + strlen(buffer) - 1;
2029 if ((c = *cp) == '\n')
2032 MD5Update(&mdContext, (unsigned char *) buffer,
2033 (unsigned int) strlen(buffer));
2036 MD5Update(&mdContext, (unsigned char *) "\r\n",
2040 while ((cc = fread(buffer, sizeof(*buffer), sizeof(buffer),
2042 MD5Update(&mdContext, (unsigned char *) buffer,
2046 /* md5 finalization. Write digest and zero md5 context */
2047 MD5Final(digest, &mdContext);
2052 /* print debugging info */
2056 fprintf(stderr, "MD5 digest=");
2057 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
2059 fprintf(stderr, "%02x", *dp & 0xff);
2060 fprintf(stderr, "\n");
2063 /* encode the digest using base64 */
2064 for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
2065 cc > 0; cc -= 3, op += 4) {
2069 bits = (*dp++ & 0xff) << 16;
2071 bits |= (*dp++ & 0xff) << 8;
2073 bits |= *dp++ & 0xff;
2076 for (bp = op + 4; bp > op; bits >>= 6)
2077 *--bp = nib2b64[bits & 0x3f];
2085 /* null terminate string */
2088 /* now make copy and return string */
2089 vp = concat(" ", outbuf, "\n", NULL);