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>
23 #include <h/mhparse.h>
26 #ifdef HAVE_SYS_TIME_H
27 # include <sys/time.h>
31 static struct swit switches[] = {
35 { "noebcdicsafe", 0 },
51 { "norfc934mode", 0 },
56 #define CONTENTIDSW 12
58 #define NCONTENTIDSW 13
71 ** Directory to place tmp files. This must
72 ** be set before these routines are called.
78 static char prefix[] = "----- =_aaaaaaaaaa";
86 int make_intermediates(char *);
87 void content_error(char *, CT, char *, ...);
90 void free_content(CT);
92 void free_encoding(CT, int);
97 static int init_decoded_content(CT);
98 static char *fgetstr(char *, int, FILE *);
99 static int user_content(FILE *, char *, char *, CT *);
100 static void set_id(CT, int);
101 static int compose_content(CT);
102 static int scan_content(CT);
103 static int build_headers(CT);
104 static CT build_mime(char *);
118 static char infile[BUFSIZ];
119 static int unlink_infile = 0;
121 static char outfile[BUFSIZ];
122 static int unlink_outfile = 0;
124 static void unlink_done(int) NORETURN;
127 int output_message(CT, char *);
128 int output_message_fp(CT, FILE *, char*);
131 int list_all_messages(CT *, int, int, int, int);
134 void set_endian(void);
137 void free_content(CT);
141 main(int argc, char **argv)
143 int sizesw = 1, headsw = 1;
144 char *cp, buf[BUFSIZ];
145 char buffer[BUFSIZ], *compfile = NULL;
146 char **argp, **arguments;
154 setlocale(LC_ALL, "");
156 invo_name = mhbasename(argv[0]);
158 /* read user profile/context */
161 arguments = getarguments(invo_name, argc, argv, 1);
164 while ((cp = *argp++)) {
165 if (cp[0] == '-' && cp[1] == '\0') {
167 adios(NULL, "cannot specify both standard input and a file");
170 listsw = 0; /* turn off -list if using std in/out */
171 verbosw = 0; /* turn off -verbose listings */
175 switch (smatch(++cp, switches)) {
177 ambigsw(cp, switches);
180 adios(NULL, "-%s unknown", cp);
183 snprintf(buf, sizeof(buf), "%s [switches] file", invo_name);
184 print_help(buf, switches, 1);
187 print_version(invo_name);
244 adios(NULL, "only one composition file allowed");
251 if ((cp = getenv("MM_NOASK")) && strcmp(cp, "1")==0)
255 ** Check if we've specified an additional profile
257 if ((cp = getenv("MHBUILD"))) {
258 if ((fp = fopen(cp, "r"))) {
259 readconfig((struct node **) 0, fp, cp, 0);
262 admonish("", "unable to read $MHBUILD profile (%s)",
268 ** Read the standard profile setup
270 if ((fp = fopen(cp = etcpath("mhn.defaults"), "r"))) {
271 readconfig((struct node **) 0, fp, cp, 0);
276 ** Check for storage directory. If defined, we
277 ** will store temporary files there. Else we
278 ** store them in standard nmh directory.
280 if ((cp = context_find(nmhstorage)) && *cp)
281 tmp = concat(cp, "/", invo_name, NULL);
283 tmp = getcpy(toabsdir(invo_name));
285 /* Check if we have a file to process */
287 adios(NULL, "need to specify a %s composition file",
291 ** Process the composition file from standard input.
293 if (compfile[0] == '-' && compfile[1] == '\0') {
294 /* copy standard input to temporary file */
295 strncpy(infile, m_mktemp(invo_name, NULL, &fp),
297 while (fgets(buffer, BUFSIZ, stdin))
302 /* build the content structures for MIME message */
303 ct = build_mime(infile);
307 /* output MIME message to this temporary file */
308 strncpy(outfile, m_mktemp(invo_name, NULL, &fp_out),
312 /* output the message */
313 output_message_fp(ct, fp_out, outfile);
316 /* output the temp file to standard output */
317 if ((fp = fopen(outfile, "r")) == NULL)
318 adios(outfile, "unable to open");
319 while (fgets(buffer, BUFSIZ, fp))
320 fputs(buffer, stdout);
334 ** Process the composition file from a file.
337 /* build the content structures for MIME message */
338 ct = build_mime(compfile);
342 /* output MIME message to this temporary file */
343 strncpy(outfile, m_mktemp2(compfile, invo_name, NULL, &fp_out),
347 /* output the message */
348 output_message_fp(ct, fp_out, outfile);
352 ** List the message info
355 list_all_messages(cts, headsw, sizesw, verbosw, debugsw);
357 /* Rename composition draft */
358 snprintf(buffer, sizeof(buffer), "%s.orig", m_backup(compfile));
359 if (rename(compfile, buffer) == NOTOK) {
360 adios(compfile, "unable to rename comp draft %s to", buffer);
363 /* Rename output file to take its place */
364 if (rename(outfile, compfile) == NOTOK) {
365 advise(outfile, "unable to rename output %s to", compfile);
366 rename(buffer, compfile);
378 unlink_done(int status)
381 ** Check if we need to remove stray temporary files.
392 ** Main routine for translating composition file
393 ** into valid MIME message. It translates the draft
394 ** into a content structure (actually a tree of content
395 ** structures). This message then can be manipulated
396 ** in various ways, including being output via
400 build_mime(char *infile)
403 char buf[BUFSIZ], name[NAMESZ];
412 /* open the composition draft */
413 if ((in = fopen(infile, "r")) == NULL)
414 adios(infile, "unable to open for reading");
417 ** Allocate space for primary (outside) content
419 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
420 adios(NULL, "out of memory");
423 ** Allocate structure for handling decoded content
424 ** for this part. We don't really need this, but
425 ** allocate it to remain consistent.
427 init_decoded_content(ct);
430 ** Parse some of the header fields in the composition
431 ** draft into the linked list of header fields for
432 ** the new MIME message.
434 for (compnum = 1, state = FLD;;) {
435 switch (state = m_getfld(state, name, buf, sizeof(buf), in)) {
441 /* abort if draft has Mime-Version header field */
442 if (!mh_strcasecmp(name, VRSN_FIELD))
443 adios(NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
446 ** abort if draft has Content-Transfer-Encoding
449 if (!mh_strcasecmp(name, ENCODING_FIELD))
450 adios(NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
452 /* ignore any Content-Type fields in the header */
453 if (!mh_strcasecmp(name, TYPE_FIELD)) {
454 while (state == FLDPLUS)
455 state = m_getfld(state, name, buf,
460 /* get copies of the buffers */
464 /* if necessary, get rest of field */
465 while (state == FLDPLUS) {
466 state = m_getfld(state, name, buf,
468 vp = add(buf, vp); /* add to prev value */
471 /* Now add the header data to the list */
472 add_header(ct, np, vp);
475 /* if this wasn't the last hdr field, then continue */
481 adios(NULL, "draft has empty body -- no directives!");
486 fseek(in, (long) (-strlen(buf)), SEEK_CUR);
491 adios(NULL, "message format error in component #%d",
495 adios(NULL, "getfld() returned %d", state);
501 ** Now add the MIME-Version header field
502 ** to the list of header fields.
504 np = getcpy(VRSN_FIELD);
505 vp = concat(" ", VRSN_VALUE, "\n", NULL);
506 add_header(ct, np, vp);
509 ** We initally assume we will find multiple contents in the
510 ** draft. So create a multipart/mixed content to hold everything.
511 ** We can remove this later, if it is not needed.
513 if (get_ctinfo("multipart/mixed", ct, 0) == NOTOK)
515 ct->c_type = CT_MULTIPART;
516 ct->c_subtype = MULTI_MIXED;
517 ct->c_file = getcpy(infile);
519 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
520 adios(NULL, "out of memory");
521 ct->c_ctparams = (void *) m;
525 ** read and parse the composition file
526 ** and the directives it contains.
528 while (fgetstr(buf, sizeof(buf) - 1, in)) {
532 if (user_content(in, infile, buf, &p) == DONE) {
533 admonish(NULL, "ignoring spurious #end");
539 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
540 adios(NULL, "out of memory");
547 ** close the composition draft since
548 ** it's not needed any longer.
552 /* check if any contents were found */
554 adios(NULL, "no content directives found");
557 ** If only one content was found, then remove and
558 ** free the outer multipart content.
560 if (!m->mp_parts->mp_next) {
563 p = m->mp_parts->mp_part;
564 m->mp_parts->mp_part = NULL;
566 /* move header fields */
567 p->c_first_hf = ct->c_first_hf;
568 p->c_last_hf = ct->c_last_hf;
569 ct->c_first_hf = NULL;
570 ct->c_last_hf = NULL;
579 ** Fill out, or expand directives. Parse and execute
580 ** commands specified by profile composition strings.
584 if ((cp = strchr(prefix, 'a')) == NULL)
585 adios(NULL, "internal error(4)");
588 ** Scan the contents. Choose a transfer encoding, and
589 ** check if prefix for multipart boundary clashes with
590 ** any of the contents.
592 while (scan_content(ct) == NOTOK) {
597 adios(NULL, "giving up trying to find a unique delimiter string");
603 /* Build the rest of the header field structures */
611 ** Set up structures for placing unencoded
612 ** content when building parts.
616 init_decoded_content(CT ct)
620 if ((ce = (CE) calloc(1, sizeof(*ce))) == NULL)
621 adios(NULL, "out of memory");
624 ct->c_ceopenfnx = open7Bit; /* since unencoded */
625 ct->c_ceclosefnx = close_encoding;
626 ct->c_cesizefnx = NULL; /* since unencoded */
633 fgetstr(char *s, int n, FILE *stream)
637 for (ep = (cp = s) + n; cp < ep; ) {
640 if (!fgets(cp, n, stream))
641 return (cp != s ? s : NULL);
642 if (cp == s && *cp != '#')
645 cp += (i = strlen(cp)) - 1;
646 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
657 ** Parse the composition draft for text and directives.
658 ** Do initial setup of Content structure.
662 user_content(FILE *in, char *file, char *buf, CT *ctp)
671 struct str2init *s2i;
676 if (buf[0] == '\n' || strcmp(buf, "#\n") == 0) {
681 /* allocate basic Content structure */
682 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
683 adios(NULL, "out of memory");
686 /* allocate basic structure for handling decoded content */
687 init_decoded_content(ct);
694 ** Handle inline text. Check if line
695 ** is one of the following forms:
697 ** 1) doesn't begin with '#' (implicit directive)
698 ** 2) begins with "##" (implicit directive)
699 ** 3) begins with "#<"
701 if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
705 char content[BUFSIZ];
709 cp = m_mktemp2(NULL, invo_name, NULL, &out);
711 adios("mhbuild", "unable to create temporary file");
713 /* use a temp file to collect the plain text lines */
714 ce->ce_file = getcpy(cp);
717 if (buf[0] == '#' && buf[1] == '<') {
718 strncpy(content, buf + 2, sizeof(content));
725 /* the directive is implicit */
726 strncpy(content, "text/plain", sizeof(content));
728 strncpy(buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
732 if (headers >= 0 && uprf(buffer, DESCR_FIELD) &&
733 buffer[i=strlen(DESCR_FIELD)] == ':') {
737 ct->c_descr = add(buffer + i + 1, ct->c_descr);
738 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
739 adios(NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
747 adios(NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
755 if (headers >= 0 && uprf(buffer, DISPO_FIELD)
756 && buffer[i = strlen(DISPO_FIELD)] == ':') {
760 ct->c_dispo = add(buffer + i + 1, ct->c_dispo);
761 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
762 adios(NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
770 adios(NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
778 if (headers != 1 || buffer[0] != '\n')
784 if ((cp = fgetstr(buffer, sizeof(buffer) - 1, in))
787 if (buffer[0] == '#') {
790 if (buffer[1] != '#')
792 for (cp = (bp = buffer) + 1; *cp; cp++)
799 ct->c_end = ftell(out);
802 /* parse content type */
803 if (get_ctinfo(content, ct, inlineD) == NOTOK)
806 for (s2i = str2cts; s2i->si_key; s2i++)
807 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
809 if (!s2i->si_key && !uprf(ci->ci_type, "X-"))
813 ** check type specified (possibly implicitly)
815 switch (ct->c_type = s2i->si_val) {
817 if (!mh_strcasecmp(ci->ci_subtype, "rfc822")) {
818 ct->c_encoding = CE_7BIT;
823 adios(NULL, "it doesn't make sense to define an in-line %s content",
824 ct->c_type == CT_MESSAGE ? "message" :
830 if ((ct->c_ctinitfnx = s2i->si_init))
831 (*ct->c_ctinitfnx) (ct);
836 fseek(in, pos, SEEK_SET);
841 ** If we've reached this point, the next line
842 ** must be some type of explicit directive.
846 adios(NULL, "The #@ directive i.e. message/external-body "
847 "is not supported anymore.");
850 /* parse directive */
851 if (get_ctinfo(buf+1, ct, 1) == NOTOK)
854 /* check directive against the list of MIME types */
855 for (s2i = str2cts; s2i->si_key; s2i++)
856 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
860 ** Check if the directive specified a valid type.
861 ** This will happen if it was one of the following forms:
867 adios(NULL, "missing subtype in \"#%s\"", ci->ci_type);
869 switch (ct->c_type = s2i->si_val) {
871 adios(NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
875 if (!mh_strcasecmp(ci->ci_subtype, "partial") ||
876 !mh_strcasecmp(ci->ci_subtype,
878 adios(NULL, "sorry, \"#%s/%s\" isn't supported", ci->ci_type, ci->ci_subtype);
881 adios(NULL, "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
885 if ((ct->c_ctinitfnx = s2i->si_init))
886 (*ct->c_ctinitfnx) (ct);
890 /* Handle [file] argument */
892 /* check if specifies command to execute */
893 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
894 for (cp = ci->ci_magic + 1; isspace(*cp); cp++)
897 adios(NULL, "empty pipe command for #%s directive", ci->ci_type);
902 /* record filename of decoded contents */
903 ce->ce_file = ci->ci_magic;
904 if (access(ce->ce_file, R_OK) == NOTOK)
905 adios("reading", "unable to access %s for", ce->ce_file);
906 if (listsw && stat(ce->ce_file, &st) != NOTOK)
907 ct->c_end = (long) st.st_size;
914 ** No [file] argument, so check profile for
915 ** method to compose content.
917 snprintf(buffer, sizeof(buffer), "%s-compose-%s/%s",
918 invo_name, ci->ci_type, ci->ci_subtype);
919 if ((cp = context_find(buffer)) == NULL || *cp == '\0') {
920 snprintf(buffer, sizeof(buffer), "%s-compose-%s",
921 invo_name, ci->ci_type);
922 if ((cp = context_find(buffer)) == NULL ||
924 content_error(NULL, ct, "don't know how to compose content");
928 ci->ci_magic = getcpy(cp);
934 ** #forw [+folder] [msgs]
936 if (!mh_strcasecmp(ci->ci_type, "forw")) {
938 char *folder, *arguments[MAXARGS];
944 ap = brkstring(ci->ci_magic, " ", "\n");
945 for (i=0; ap[i] && i<MAXARGS-1; i++) {
946 arguments[i] = ap[i];
951 arguments[0] = seq_cur;
956 /* search the arguments for a folder name */
957 for (ap = arguments; *ap; ap++) {
959 if (*cp == '+' || *cp == '@') {
961 adios(NULL, "only one folder per #forw directive");
963 folder = getcpy(expandfol(cp));
967 /* else, use the current folder */
969 folder = getcpy(getcurfol());
971 if (!(mp = folder_read(folder)))
972 adios(NULL, "unable to read folder %s", folder);
973 for (ap = arguments; *ap; ap++) {
975 if (*cp != '+' && *cp != '@')
976 if (!m_convert(mp, cp))
983 ** If there is more than one message to include, make this
984 ** a content of type "multipart/digest" and insert each message
985 ** as a subpart. If there is only one message, then make this
986 ** a content of type "message/rfc822".
988 if (mp->numsel > 1) {
989 /* we are forwarding multiple messages */
990 if (get_ctinfo("multipart/digest", ct, 0) == NOTOK)
992 ct->c_type = CT_MULTIPART;
993 ct->c_subtype = MULTI_DIGEST;
995 if ((m = (struct multipart *)
996 calloc(1, sizeof(*m))) == NULL)
997 adios(NULL, "out of memory");
998 ct->c_ctparams = (void *) m;
1001 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
1002 if (is_selected(mp, msgnum)) {
1007 if ((p = (CT) calloc(1, sizeof(*p)))
1009 adios(NULL, "out of memory");
1010 init_decoded_content(p);
1012 if (get_ctinfo("message/rfc822", p, 0)
1015 p->c_type = CT_MESSAGE;
1016 p->c_subtype = MESSAGE_RFC822;
1018 snprintf(buffer, sizeof(buffer),
1019 "%s/%d", mp->foldpath,
1021 pe->ce_file = getcpy(buffer);
1022 if (listsw && stat(pe->ce_file, &st)
1024 p->c_end = (long) st.st_size;
1026 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
1027 adios(NULL, "out of memory");
1029 pp = &part->mp_next;
1034 /* we are forwarding one message */
1035 if (get_ctinfo("message/rfc822", ct, 0) == NOTOK)
1037 ct->c_type = CT_MESSAGE;
1038 ct->c_subtype = MESSAGE_RFC822;
1040 msgnum = mp->lowsel;
1041 snprintf(buffer, sizeof(buffer), "%s/%d",
1042 mp->foldpath, msgnum);
1043 ce->ce_file = getcpy(buffer);
1044 if (listsw && stat(ce->ce_file, &st) != NOTOK)
1045 ct->c_end = (long) st.st_size;
1048 folder_free(mp); /* free folder/message structure */
1055 if (!mh_strcasecmp(ci->ci_type, "end")) {
1062 ** #begin [ alternative | parallel ]
1064 if (!mh_strcasecmp(ci->ci_type, "begin")) {
1065 if (!ci->ci_magic) {
1067 cp = SubMultiPart[vrsn - 1].kv_key;
1068 } else if (!mh_strcasecmp(ci->ci_magic, "alternative")) {
1069 vrsn = MULTI_ALTERNATE;
1070 cp = SubMultiPart[vrsn - 1].kv_key;
1071 } else if (!mh_strcasecmp(ci->ci_magic, "parallel")) {
1072 vrsn = MULTI_PARALLEL;
1073 cp = SubMultiPart[vrsn - 1].kv_key;
1074 } else if (uprf(ci->ci_magic, "digest")) {
1077 vrsn = MULTI_UNKNOWN;
1082 snprintf(buffer, sizeof(buffer), "multipart/%s", cp);
1083 if (get_ctinfo(buffer, ct, 0) == NOTOK)
1085 ct->c_type = CT_MULTIPART;
1086 ct->c_subtype = vrsn;
1088 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
1089 adios(NULL, "out of memory");
1090 ct->c_ctparams = (void *) m;
1093 while (fgetstr(buffer, sizeof(buffer) - 1, in)) {
1097 if (user_content(in, file, buffer, &p) == DONE) {
1099 adios(NULL, "empty \"#begin ... #end\" sequence");
1105 if ((part = (struct part *)
1106 calloc(1, sizeof(*part))) == NULL)
1107 adios(NULL, "out of memory");
1109 pp = &part->mp_next;
1112 admonish(NULL, "premature end-of-file, missing #end");
1117 ** Unknown directive
1119 adios(NULL, "unknown directive \"#%s\"", ci->ci_type);
1120 return NOTOK; /* NOT REACHED */
1125 set_id(CT ct, int top)
1129 static time_t clock = 0;
1130 static char *msgfmt;
1134 snprintf(msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
1135 (int) getpid(), (long) clock, LocalName());
1137 msgfmt = getcpy(msgid);
1139 snprintf(msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
1140 ct->c_id = getcpy(msgid);
1144 static char ebcdicsafe[0x100] = {
1145 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1146 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1147 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1148 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1149 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
1150 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1151 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1152 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1153 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1154 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1155 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1156 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
1157 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1158 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1159 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1160 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1161 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1162 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1163 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1164 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1165 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1166 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1167 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1168 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1169 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1170 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1171 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1172 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1173 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1174 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1175 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1176 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1181 ** Fill out, or expand the various contents in the composition
1182 ** draft. Read-in any necessary files. Parse and execute any
1183 ** commands specified by profile composition strings.
1187 compose_content(CT ct)
1189 CE ce = ct->c_cefile;
1191 switch (ct->c_type) {
1196 char partnam[BUFSIZ];
1197 struct multipart *m = (struct multipart *) ct->c_ctparams;
1201 snprintf(partnam, sizeof(partnam), "%s.",
1203 pp = partnam + strlen(partnam);
1208 /* first, we call compose_content on all the subparts */
1209 for (part = m->mp_parts, partnum = 1; part;
1210 part = part->mp_next, partnum++) {
1211 CT p = part->mp_part;
1213 sprintf(pp, "%d", partnum);
1214 p->c_partno = getcpy(partnam);
1215 if (compose_content(p) == NOTOK)
1220 ** If the -rfc934mode switch is given, then check all
1221 ** the subparts of a multipart/digest. If they are all
1222 ** message/rfc822, then mark this content and all
1223 ** subparts with the rfc934 compatibility mode flag.
1225 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
1228 for (part = m->mp_parts; part; part = part->mp_next) {
1229 CT p = part->mp_part;
1231 if (p->c_subtype != MESSAGE_RFC822) {
1236 ct->c_rfc934 = is934;
1237 for (part = m->mp_parts; part; part = part->mp_next) {
1238 CT p = part->mp_part;
1240 if ((p->c_rfc934 = is934))
1246 ct->c_end = (partnum = strlen(prefix) + 2) + 2;
1250 for (part = m->mp_parts; part; part = part->mp_next)
1251 ct->c_end += part->mp_part->c_end + partnum;
1257 /* Nothing to do for type message */
1261 ** Discrete types (text/application/audio/image/video)
1266 int xstdout, len, buflen;
1267 char *bp, **ap, *cp;
1268 char *vec[4], buffer[BUFSIZ];
1270 CI ci = &ct->c_ctinfo;
1273 if (!(cp = ci->ci_magic))
1274 adios(NULL, "internal error(5)");
1276 tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1277 if (tfile == NULL) {
1278 adios("mhbuild", "unable to create temporary file");
1280 ce->ce_file = getcpy(tfile);
1285 /* Get buffer ready to go */
1288 buflen = sizeof(buffer);
1291 ** Parse composition string into buffer
1293 for ( ; *cp; cp++) {
1299 ** insert parameters from
1305 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1306 snprintf(bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1316 /* %f, and stdout is not-redirected */
1322 ** insert temporary filename
1323 ** where content should be
1326 snprintf(bp, buflen, "%s", ce->ce_file);
1330 /* insert content subtype */
1331 strncpy(bp, ci->ci_subtype, buflen);
1335 /* insert character % */
1356 printf("composing content %s/%s from command\n\t%s\n", ci->ci_type, ci->ci_subtype, buffer);
1358 fflush(stdout); /* not sure if need for -noverbose */
1365 if ((out = fopen(ce->ce_file, "w")) == NULL)
1366 adios(ce->ce_file, "unable to open for writing");
1368 switch (child_id = fork()) {
1370 adios("fork", "unable to fork");
1375 dup2(fileno(out), 1);
1377 execvp("/bin/sh", vec);
1378 fprintf(stderr, "unable to exec ");
1385 if (pidXwait(child_id, NULL))
1391 /* Check size of file */
1392 if (listsw && ct->c_end == 0L) {
1395 if (stat(ce->ce_file, &st) != NOTOK)
1396 ct->c_end = (long) st.st_size;
1406 ** Scan the content.
1408 ** 1) choose a transfer encoding.
1409 ** 2) check for clashes with multipart boundary string.
1410 ** 3) for text content, figure out which character set is being used.
1412 ** If there is a clash with one of the contents and the multipart boundary,
1413 ** this function will exit with NOTOK. This will cause the scanning process
1414 ** to be repeated with a different multipart boundary. It is possible
1415 ** (although highly unlikely) that this scan will be repeated multiple times.
1422 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1423 int checklinelen = 0, linelen = 0; /* check for long lines */
1424 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1425 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1426 int checkebcdic = 0, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
1427 unsigned char *cp = NULL, buffer[BUFSIZ];
1428 struct text *t = NULL;
1430 CE ce = ct->c_cefile;
1433 ** handle multipart by scanning all subparts
1434 ** and then checking their encoding.
1436 if (ct->c_type == CT_MULTIPART) {
1437 struct multipart *m = (struct multipart *) ct->c_ctparams;
1440 /* initially mark the domain of enclosing multipart as 7bit */
1441 ct->c_encoding = CE_7BIT;
1443 for (part = m->mp_parts; part; part = part->mp_next) {
1444 CT p = part->mp_part;
1446 if (scan_content(p) == NOTOK) {
1447 /* choose encoding for subpart */
1452 ** if necessary, enlarge encoding for enclosing
1455 if (p->c_encoding == CE_BINARY)
1456 ct->c_encoding = CE_BINARY;
1457 if (p->c_encoding == CE_8BIT &&
1458 ct->c_encoding != CE_BINARY)
1459 ct->c_encoding = CE_8BIT;
1466 ** Decide what to check while scanning this content.
1468 switch (ct->c_type) {
1472 if (ct->c_subtype == TEXT_PLAIN) {
1477 checkebcdic = ebcdicsw;
1483 case CT_APPLICATION:
1485 checkebcdic = ebcdicsw;
1503 ** Don't check anything for these types,
1504 ** since we are forcing use of base64.
1515 ** Scan the unencoded content
1517 if (check8bit || checklinelen || checklinespace || checkboundary) {
1518 if ((in = fopen(ce->ce_file, "r")) == NULL)
1519 adios(ce->ce_file, "unable to open for reading");
1520 len = strlen(prefix);
1522 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1524 ** Check for 8bit data.
1527 for (cp = buffer; *cp; cp++) {
1528 if (!isascii(*cp)) {
1530 /* no need to keep checking */
1534 ** Check if character is ebcdic-safe.
1535 ** We only check this if also checking
1538 if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1540 /* no need to keep checking */
1547 ** Check line length.
1549 if (checklinelen && (strlen(buffer) > CPERLIN + 1)) {
1551 checklinelen = 0; /* no need to keep checking */
1555 ** Check if line ends with a space.
1557 if (checklinespace &&
1558 (cp = buffer + strlen(buffer) - 2) >
1559 buffer && isspace(*cp)) {
1561 /* no need to keep checking */
1566 ** Check if content contains a line that clashes
1567 ** with our standard boundary for multipart messages.
1569 if (checkboundary && buffer[0] == '-' &&
1571 for (cp = buffer + strlen(buffer) - 1;
1576 if (strncmp(buffer + 2, prefix, len)==0 &&
1577 isdigit(buffer[2 + len])) {
1579 /* no need to keep checking */
1588 ** Decide which transfer encoding to use.
1590 switch (ct->c_type) {
1593 ** If the text content didn't specify a character
1594 ** set, we need to figure out which one was used.
1596 t = (struct text *) ct->c_ctparams;
1597 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1598 CI ci = &ct->c_ctinfo;
1601 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1605 t->tx_charset = CHARSET_UNKNOWN;
1606 *ap = concat("charset=", write_charset_8bit(),
1609 t->tx_charset = CHARSET_USASCII;
1610 *ap = getcpy("charset=us-ascii");
1613 cp = strchr(*ap++, '=');
1619 if (contains8bit || ebcdicunsafe || linelen || linespace)
1620 ct->c_encoding = CE_QUOTED;
1622 ct->c_encoding = CE_7BIT;
1625 case CT_APPLICATION:
1626 /* For application type, use base64, except when postscript */
1627 if (contains8bit || ebcdicunsafe || linelen || linespace)
1628 ct->c_encoding = (ct->c_subtype ==
1629 APPLICATION_POSTSCRIPT) ?
1630 CE_QUOTED : CE_BASE64;
1632 ct->c_encoding = CE_7BIT;
1636 ct->c_encoding = CE_7BIT;
1642 /* For audio, image, and video contents, just use base64 */
1643 ct->c_encoding = CE_BASE64;
1647 return (boundaryclash ? NOTOK : OK);
1652 ** Scan the content structures, and build header
1653 ** fields that will need to be output into the
1658 build_headers(CT ct)
1662 char *np, *vp, buffer[BUFSIZ];
1663 CI ci = &ct->c_ctinfo;
1666 ** If message is type multipart, then add the multipart
1667 ** boundary to the list of attribute/value pairs.
1669 if (ct->c_type == CT_MULTIPART) {
1671 static int level = 0; /* store nesting level */
1675 snprintf(buffer, sizeof(buffer), "boundary=%s%d",
1677 cp = strchr(*ap++ = getcpy(buffer), '=');
1684 ** Skip the output of Content-Type, parameters, content
1685 ** description and disposition, and Content-ID if the
1686 ** content is of type "message" and the rfc934 compatibility
1687 ** flag is set (which means we are inside multipart/digest
1688 ** and the switch -rfc934mode was given).
1690 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1694 ** output the content type and subtype
1696 np = getcpy(TYPE_FIELD);
1697 vp = concat(" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1699 /* keep track of length of line */
1700 len = strlen(TYPE_FIELD) + strlen(ci->ci_type) +
1701 strlen(ci->ci_subtype) + 3;
1704 ** Append the attribute/value pairs to
1705 ** the end of the Content-Type line.
1707 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1711 snprintf(buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1712 if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) {
1713 vp = add("\n\t", vp);
1719 vp = add(buffer, vp);
1724 ** Append any RFC-822 comment to the end of
1725 ** the Content-Type line.
1727 if (ci->ci_comment) {
1728 snprintf(buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1729 if (len + 1 + (cc = 2 + strlen(ci->ci_comment)) >= CPERLIN) {
1730 vp = add("\n\t", vp);
1736 vp = add(buffer, vp);
1740 add_header(ct, np, vp);
1743 ** output the Content-ID, unless disabled by -nocontentid
1745 if (contentidsw && ct->c_id) {
1746 np = getcpy(ID_FIELD);
1747 vp = concat(" ", ct->c_id, NULL);
1748 add_header(ct, np, vp);
1752 ** output the Content-Description
1755 np = getcpy(DESCR_FIELD);
1756 vp = concat(" ", ct->c_descr, NULL);
1757 add_header(ct, np, vp);
1761 ** output the Content-Disposition
1764 np = getcpy(DISPO_FIELD);
1765 vp = concat(" ", ct->c_dispo, NULL);
1766 add_header(ct, np, vp);
1771 ** output the Content-Transfer-Encoding
1773 switch (ct->c_encoding) {
1775 /* Nothing to output */
1779 if (ct->c_type == CT_MESSAGE)
1780 adios(NULL, "internal error, invalid encoding");
1782 np = getcpy(ENCODING_FIELD);
1783 vp = concat(" ", "8bit", "\n", NULL);
1784 add_header(ct, np, vp);
1788 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1789 adios(NULL, "internal error, invalid encoding");
1791 np = getcpy(ENCODING_FIELD);
1792 vp = concat(" ", "quoted-printable", "\n", NULL);
1793 add_header(ct, np, vp);
1797 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1798 adios(NULL, "internal error, invalid encoding");
1800 np = getcpy(ENCODING_FIELD);
1801 vp = concat(" ", "base64", "\n", NULL);
1802 add_header(ct, np, vp);
1806 if (ct->c_type == CT_MESSAGE)
1807 adios(NULL, "internal error, invalid encoding");
1809 np = getcpy(ENCODING_FIELD);
1810 vp = concat(" ", "binary", "\n", NULL);
1811 add_header(ct, np, vp);
1815 adios(NULL, "unknown transfer encoding in content");
1820 ** Additional content specific header processing
1822 switch (ct->c_type) {
1825 struct multipart *m;
1828 m = (struct multipart *) ct->c_ctparams;
1829 for (part = m->mp_parts; part; part = part->mp_next) {