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 },
47 { "norfc934mode", 0 },
52 #define CONTENTIDSW 10
54 #define NCONTENTIDSW 11
67 ** Directory to place tmp files. This must
68 ** be set before these routines are called.
74 static char prefix[] = "----- =_aaaaaaaaaa";
82 int make_intermediates(char *);
83 void content_error(char *, CT, char *, ...);
86 void free_content(CT);
88 void free_encoding(CT, int);
93 static int init_decoded_content(CT);
94 static char *fgetstr(char *, int, FILE *);
95 static int user_content(FILE *, char *, char *, CT *);
96 static void set_id(CT, int);
97 static int compose_content(CT);
98 static int scan_content(CT);
99 static int build_headers(CT);
100 static CT build_mime(char *);
114 static char infile[BUFSIZ];
115 static int unlink_infile = 0;
117 static char outfile[BUFSIZ];
118 static int unlink_outfile = 0;
120 static void unlink_done(int) NORETURN;
123 int output_message(CT, char *);
124 int output_message_fp(CT, FILE *, char*);
127 int list_all_messages(CT *, int, int, int);
130 void set_endian(void);
133 void free_content(CT);
137 main(int argc, char **argv)
140 char *cp, buf[BUFSIZ];
141 char buffer[BUFSIZ], *compfile = NULL;
142 char **argp, **arguments;
150 setlocale(LC_ALL, "");
152 invo_name = mhbasename(argv[0]);
154 /* read user profile/context */
157 arguments = getarguments(invo_name, argc, argv, 1);
160 while ((cp = *argp++)) {
161 if (cp[0] == '-' && cp[1] == '\0') {
163 adios(NULL, "cannot specify both standard input and a file");
166 listsw = 0; /* turn off -list if using std in/out */
167 verbosw = 0; /* turn off -verbose listings */
171 switch (smatch(++cp, switches)) {
173 ambigsw(cp, switches);
176 adios(NULL, "-%s unknown", cp);
179 snprintf(buf, sizeof(buf), "%s [switches] file", invo_name);
180 print_help(buf, switches, 1);
183 print_version(invo_name);
233 adios(NULL, "only one composition file allowed");
240 if ((cp = getenv("MM_NOASK")) && strcmp(cp, "1")==0)
244 ** Check if we've specified an additional profile
246 if ((cp = getenv("MHBUILD"))) {
247 if ((fp = fopen(cp, "r"))) {
248 readconfig((struct node **) 0, fp, cp, 0);
251 admonish("", "unable to read $MHBUILD profile (%s)",
257 ** Read the standard profile setup
259 if ((fp = fopen(cp = etcpath("mhn.defaults"), "r"))) {
260 readconfig((struct node **) 0, fp, cp, 0);
265 ** Check for storage directory. If defined, we
266 ** will store temporary files there. Else we
267 ** store them in standard nmh directory.
269 if ((cp = context_find(nmhstorage)) && *cp)
270 tmp = concat(cp, "/", invo_name, NULL);
272 tmp = getcpy(toabsdir(invo_name));
274 /* Check if we have a file to process */
276 adios(NULL, "need to specify a %s composition file",
280 ** Process the composition file from standard input.
282 if (compfile[0] == '-' && compfile[1] == '\0') {
283 /* copy standard input to temporary file */
284 strncpy(infile, m_mktemp(invo_name, NULL, &fp),
286 while (fgets(buffer, BUFSIZ, stdin))
291 /* build the content structures for MIME message */
292 ct = build_mime(infile);
296 /* output MIME message to this temporary file */
297 strncpy(outfile, m_mktemp(invo_name, NULL, &fp_out),
301 /* output the message */
302 output_message_fp(ct, fp_out, outfile);
305 /* output the temp file to standard output */
306 if ((fp = fopen(outfile, "r")) == NULL)
307 adios(outfile, "unable to open");
308 while (fgets(buffer, BUFSIZ, fp))
309 fputs(buffer, stdout);
323 ** Process the composition file from a file.
326 /* build the content structures for MIME message */
327 ct = build_mime(compfile);
331 /* output MIME message to this temporary file */
332 strncpy(outfile, m_mktemp2(compfile, invo_name, NULL, &fp_out),
336 /* output the message */
337 output_message_fp(ct, fp_out, outfile);
341 ** List the message info
344 list_all_messages(cts, headsw, verbosw, debugsw);
346 /* Rename composition draft */
347 snprintf(buffer, sizeof(buffer), "%s.orig", m_backup(compfile));
348 if (rename(compfile, buffer) == NOTOK) {
349 adios(compfile, "unable to rename comp draft %s to", buffer);
352 /* Rename output file to take its place */
353 if (rename(outfile, compfile) == NOTOK) {
354 advise(outfile, "unable to rename output %s to", compfile);
355 rename(buffer, compfile);
367 unlink_done(int status)
370 ** Check if we need to remove stray temporary files.
381 ** Main routine for translating composition file
382 ** into valid MIME message. It translates the draft
383 ** into a content structure (actually a tree of content
384 ** structures). This message then can be manipulated
385 ** in various ways, including being output via
389 build_mime(char *infile)
392 char buf[BUFSIZ], name[NAMESZ];
401 /* open the composition draft */
402 if ((in = fopen(infile, "r")) == NULL)
403 adios(infile, "unable to open for reading");
406 ** Allocate space for primary (outside) content
408 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
409 adios(NULL, "out of memory");
412 ** Allocate structure for handling decoded content
413 ** for this part. We don't really need this, but
414 ** allocate it to remain consistent.
416 init_decoded_content(ct);
419 ** Parse some of the header fields in the composition
420 ** draft into the linked list of header fields for
421 ** the new MIME message.
423 for (compnum = 1, state = FLD;;) {
424 switch (state = m_getfld(state, name, buf, sizeof(buf), in)) {
430 /* abort if draft has Mime-Version header field */
431 if (!mh_strcasecmp(name, VRSN_FIELD))
432 adios(NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
435 ** abort if draft has Content-Transfer-Encoding
438 if (!mh_strcasecmp(name, ENCODING_FIELD))
439 adios(NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
441 /* ignore any Content-Type fields in the header */
442 if (!mh_strcasecmp(name, TYPE_FIELD)) {
443 while (state == FLDPLUS)
444 state = m_getfld(state, name, buf,
449 /* get copies of the buffers */
453 /* if necessary, get rest of field */
454 while (state == FLDPLUS) {
455 state = m_getfld(state, name, buf,
457 vp = add(buf, vp); /* add to prev value */
460 /* Now add the header data to the list */
461 add_header(ct, np, vp);
464 /* if this wasn't the last hdr field, then continue */
470 adios(NULL, "draft has empty body -- no directives!");
475 fseek(in, (long) (-strlen(buf)), SEEK_CUR);
480 adios(NULL, "message format error in component #%d",
484 adios(NULL, "getfld() returned %d", state);
490 ** Now add the MIME-Version header field
491 ** to the list of header fields.
493 np = getcpy(VRSN_FIELD);
494 vp = concat(" ", VRSN_VALUE, "\n", NULL);
495 add_header(ct, np, vp);
498 ** We initally assume we will find multiple contents in the
499 ** draft. So create a multipart/mixed content to hold everything.
500 ** We can remove this later, if it is not needed.
502 if (get_ctinfo("multipart/mixed", ct, 0) == NOTOK)
504 ct->c_type = CT_MULTIPART;
505 ct->c_subtype = MULTI_MIXED;
506 ct->c_file = getcpy(infile);
508 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
509 adios(NULL, "out of memory");
510 ct->c_ctparams = (void *) m;
514 ** read and parse the composition file
515 ** and the directives it contains.
517 while (fgetstr(buf, sizeof(buf) - 1, in)) {
521 if (user_content(in, infile, buf, &p) == DONE) {
522 admonish(NULL, "ignoring spurious #end");
528 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
529 adios(NULL, "out of memory");
536 ** close the composition draft since
537 ** it's not needed any longer.
541 /* check if any contents were found */
543 adios(NULL, "no content directives found");
546 ** If only one content was found, then remove and
547 ** free the outer multipart content.
549 if (!m->mp_parts->mp_next) {
552 p = m->mp_parts->mp_part;
553 m->mp_parts->mp_part = NULL;
555 /* move header fields */
556 p->c_first_hf = ct->c_first_hf;
557 p->c_last_hf = ct->c_last_hf;
558 ct->c_first_hf = NULL;
559 ct->c_last_hf = NULL;
568 ** Fill out, or expand directives. Parse and execute
569 ** commands specified by profile composition strings.
573 if ((cp = strchr(prefix, 'a')) == NULL)
574 adios(NULL, "internal error(4)");
577 ** Scan the contents. Choose a transfer encoding, and
578 ** check if prefix for multipart boundary clashes with
579 ** any of the contents.
581 while (scan_content(ct) == NOTOK) {
586 adios(NULL, "giving up trying to find a unique delimiter string");
592 /* Build the rest of the header field structures */
600 ** Set up structures for placing unencoded
601 ** content when building parts.
605 init_decoded_content(CT ct)
609 if ((ce = (CE) calloc(1, sizeof(*ce))) == NULL)
610 adios(NULL, "out of memory");
613 ct->c_ceopenfnx = open7Bit; /* since unencoded */
614 ct->c_ceclosefnx = close_encoding;
615 ct->c_cesizefnx = NULL; /* since unencoded */
622 fgetstr(char *s, int n, FILE *stream)
626 for (ep = (cp = s) + n; cp < ep; ) {
629 if (!fgets(cp, n, stream))
630 return (cp != s ? s : NULL);
631 if (cp == s && *cp != '#')
634 cp += (i = strlen(cp)) - 1;
635 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
646 ** Parse the composition draft for text and directives.
647 ** Do initial setup of Content structure.
651 user_content(FILE *in, char *file, char *buf, CT *ctp)
660 struct str2init *s2i;
665 if (buf[0] == '\n' || strcmp(buf, "#\n") == 0) {
670 /* allocate basic Content structure */
671 if ((ct = (CT) calloc(1, sizeof(*ct))) == NULL)
672 adios(NULL, "out of memory");
675 /* allocate basic structure for handling decoded content */
676 init_decoded_content(ct);
683 ** Handle inline text. Check if line
684 ** is one of the following forms:
686 ** 1) doesn't begin with '#' (implicit directive)
687 ** 2) begins with "##" (implicit directive)
688 ** 3) begins with "#<"
690 if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
694 char content[BUFSIZ];
698 cp = m_mktemp2(NULL, invo_name, NULL, &out);
700 adios("mhbuild", "unable to create temporary file");
702 /* use a temp file to collect the plain text lines */
703 ce->ce_file = getcpy(cp);
706 if (buf[0] == '#' && buf[1] == '<') {
707 strncpy(content, buf + 2, sizeof(content));
714 /* the directive is implicit */
715 strncpy(content, "text/plain", sizeof(content));
717 strncpy(buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
721 if (headers >= 0 && uprf(buffer, DESCR_FIELD) &&
722 buffer[i=strlen(DESCR_FIELD)] == ':') {
726 ct->c_descr = add(buffer + i + 1, ct->c_descr);
727 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
728 adios(NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
736 adios(NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
744 if (headers >= 0 && uprf(buffer, DISPO_FIELD)
745 && buffer[i = strlen(DISPO_FIELD)] == ':') {
749 ct->c_dispo = add(buffer + i + 1, ct->c_dispo);
750 if (!fgetstr(buffer, sizeof(buffer) - 1, in))
751 adios(NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
759 adios(NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
767 if (headers != 1 || buffer[0] != '\n')
773 if ((cp = fgetstr(buffer, sizeof(buffer) - 1, in))
776 if (buffer[0] == '#') {
779 if (buffer[1] != '#')
781 for (cp = (bp = buffer) + 1; *cp; cp++)
788 ct->c_end = ftell(out);
791 /* parse content type */
792 if (get_ctinfo(content, ct, inlineD) == NOTOK)
795 for (s2i = str2cts; s2i->si_key; s2i++)
796 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
798 if (!s2i->si_key && !uprf(ci->ci_type, "X-"))
802 ** check type specified (possibly implicitly)
804 switch (ct->c_type = s2i->si_val) {
806 if (!mh_strcasecmp(ci->ci_subtype, "rfc822")) {
807 ct->c_encoding = CE_7BIT;
812 adios(NULL, "it doesn't make sense to define an in-line %s content",
813 ct->c_type == CT_MESSAGE ? "message" :
819 if ((ct->c_ctinitfnx = s2i->si_init))
820 (*ct->c_ctinitfnx) (ct);
825 fseek(in, pos, SEEK_SET);
830 ** If we've reached this point, the next line
831 ** must be some type of explicit directive.
835 adios(NULL, "The #@ directive i.e. message/external-body "
836 "is not supported anymore.");
839 /* parse directive */
840 if (get_ctinfo(buf+1, ct, 1) == NOTOK)
843 /* check directive against the list of MIME types */
844 for (s2i = str2cts; s2i->si_key; s2i++)
845 if (!mh_strcasecmp(ci->ci_type, s2i->si_key))
849 ** Check if the directive specified a valid type.
850 ** This will happen if it was one of the following forms:
856 adios(NULL, "missing subtype in \"#%s\"", ci->ci_type);
858 switch (ct->c_type = s2i->si_val) {
860 adios(NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
864 if (!mh_strcasecmp(ci->ci_subtype, "partial") ||
865 !mh_strcasecmp(ci->ci_subtype,
867 adios(NULL, "sorry, \"#%s/%s\" isn't supported", ci->ci_type, ci->ci_subtype);
870 adios(NULL, "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"", ci->ci_type, ci->ci_subtype);
874 if ((ct->c_ctinitfnx = s2i->si_init))
875 (*ct->c_ctinitfnx) (ct);
879 /* Handle [file] argument */
881 /* check if specifies command to execute */
882 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
883 for (cp = ci->ci_magic + 1; isspace(*cp); cp++)
886 adios(NULL, "empty pipe command for #%s directive", ci->ci_type);
891 /* record filename of decoded contents */
892 ce->ce_file = ci->ci_magic;
893 if (access(ce->ce_file, R_OK) == NOTOK)
894 adios("reading", "unable to access %s for", ce->ce_file);
895 if (listsw && stat(ce->ce_file, &st) != NOTOK)
896 ct->c_end = (long) st.st_size;
903 ** No [file] argument, so check profile for
904 ** method to compose content.
906 snprintf(buffer, sizeof(buffer), "%s-compose-%s/%s",
907 invo_name, ci->ci_type, ci->ci_subtype);
908 if ((cp = context_find(buffer)) == NULL || *cp == '\0') {
909 snprintf(buffer, sizeof(buffer), "%s-compose-%s",
910 invo_name, ci->ci_type);
911 if ((cp = context_find(buffer)) == NULL ||
913 content_error(NULL, ct, "don't know how to compose content");
917 ci->ci_magic = getcpy(cp);
923 ** #forw [+folder] [msgs]
925 if (!mh_strcasecmp(ci->ci_type, "forw")) {
927 char *folder, *arguments[MAXARGS];
933 ap = brkstring(ci->ci_magic, " ", "\n");
934 for (i=0; ap[i] && i<MAXARGS-1; i++) {
935 arguments[i] = ap[i];
940 arguments[0] = seq_cur;
945 /* search the arguments for a folder name */
946 for (ap = arguments; *ap; ap++) {
948 if (*cp == '+' || *cp == '@') {
950 adios(NULL, "only one folder per #forw directive");
952 folder = getcpy(expandfol(cp));
956 /* else, use the current folder */
958 folder = getcpy(getcurfol());
960 if (!(mp = folder_read(folder)))
961 adios(NULL, "unable to read folder %s", folder);
962 for (ap = arguments; *ap; ap++) {
964 if (*cp != '+' && *cp != '@')
965 if (!m_convert(mp, cp))
972 ** If there is more than one message to include, make this
973 ** a content of type "multipart/digest" and insert each message
974 ** as a subpart. If there is only one message, then make this
975 ** a content of type "message/rfc822".
977 if (mp->numsel > 1) {
978 /* we are forwarding multiple messages */
979 if (get_ctinfo("multipart/digest", ct, 0) == NOTOK)
981 ct->c_type = CT_MULTIPART;
982 ct->c_subtype = MULTI_DIGEST;
984 if ((m = (struct multipart *)
985 calloc(1, sizeof(*m))) == NULL)
986 adios(NULL, "out of memory");
987 ct->c_ctparams = (void *) m;
990 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
991 if (is_selected(mp, msgnum)) {
996 if ((p = (CT) calloc(1, sizeof(*p)))
998 adios(NULL, "out of memory");
999 init_decoded_content(p);
1001 if (get_ctinfo("message/rfc822", p, 0)
1004 p->c_type = CT_MESSAGE;
1005 p->c_subtype = MESSAGE_RFC822;
1007 snprintf(buffer, sizeof(buffer),
1008 "%s/%d", mp->foldpath,
1010 pe->ce_file = getcpy(buffer);
1011 if (listsw && stat(pe->ce_file, &st)
1013 p->c_end = (long) st.st_size;
1015 if ((part = (struct part *) calloc(1, sizeof(*part))) == NULL)
1016 adios(NULL, "out of memory");
1018 pp = &part->mp_next;
1023 /* we are forwarding one message */
1024 if (get_ctinfo("message/rfc822", ct, 0) == NOTOK)
1026 ct->c_type = CT_MESSAGE;
1027 ct->c_subtype = MESSAGE_RFC822;
1029 msgnum = mp->lowsel;
1030 snprintf(buffer, sizeof(buffer), "%s/%d",
1031 mp->foldpath, msgnum);
1032 ce->ce_file = getcpy(buffer);
1033 if (listsw && stat(ce->ce_file, &st) != NOTOK)
1034 ct->c_end = (long) st.st_size;
1037 folder_free(mp); /* free folder/message structure */
1044 if (!mh_strcasecmp(ci->ci_type, "end")) {
1051 ** #begin [ alternative | parallel ]
1053 if (!mh_strcasecmp(ci->ci_type, "begin")) {
1054 if (!ci->ci_magic) {
1056 cp = SubMultiPart[vrsn - 1].kv_key;
1057 } else if (!mh_strcasecmp(ci->ci_magic, "alternative")) {
1058 vrsn = MULTI_ALTERNATE;
1059 cp = SubMultiPart[vrsn - 1].kv_key;
1060 } else if (!mh_strcasecmp(ci->ci_magic, "parallel")) {
1061 vrsn = MULTI_PARALLEL;
1062 cp = SubMultiPart[vrsn - 1].kv_key;
1063 } else if (uprf(ci->ci_magic, "digest")) {
1066 vrsn = MULTI_UNKNOWN;
1071 snprintf(buffer, sizeof(buffer), "multipart/%s", cp);
1072 if (get_ctinfo(buffer, ct, 0) == NOTOK)
1074 ct->c_type = CT_MULTIPART;
1075 ct->c_subtype = vrsn;
1077 if ((m = (struct multipart *) calloc(1, sizeof(*m))) == NULL)
1078 adios(NULL, "out of memory");
1079 ct->c_ctparams = (void *) m;
1082 while (fgetstr(buffer, sizeof(buffer) - 1, in)) {
1086 if (user_content(in, file, buffer, &p) == DONE) {
1088 adios(NULL, "empty \"#begin ... #end\" sequence");
1094 if ((part = (struct part *)
1095 calloc(1, sizeof(*part))) == NULL)
1096 adios(NULL, "out of memory");
1098 pp = &part->mp_next;
1101 admonish(NULL, "premature end-of-file, missing #end");
1106 ** Unknown directive
1108 adios(NULL, "unknown directive \"#%s\"", ci->ci_type);
1109 return NOTOK; /* NOT REACHED */
1114 set_id(CT ct, int top)
1118 static time_t clock = 0;
1119 static char *msgfmt;
1123 snprintf(msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
1124 (int) getpid(), (long) clock, LocalName());
1126 msgfmt = getcpy(msgid);
1128 snprintf(msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
1129 ct->c_id = getcpy(msgid);
1133 static char ebcdicsafe[0x100] = {
1134 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1135 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1136 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1137 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1138 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
1139 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1140 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1141 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1142 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1143 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1144 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1145 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
1146 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1147 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1148 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
1149 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1150 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1151 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1152 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1153 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1154 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1155 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1156 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1157 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1158 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1159 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1160 0x00, 0x00, 0x00, 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
1170 ** Fill out, or expand the various contents in the composition
1171 ** draft. Read-in any necessary files. Parse and execute any
1172 ** commands specified by profile composition strings.
1176 compose_content(CT ct)
1178 CE ce = ct->c_cefile;
1180 switch (ct->c_type) {
1185 char partnam[BUFSIZ];
1186 struct multipart *m = (struct multipart *) ct->c_ctparams;
1190 snprintf(partnam, sizeof(partnam), "%s.",
1192 pp = partnam + strlen(partnam);
1197 /* first, we call compose_content on all the subparts */
1198 for (part = m->mp_parts, partnum = 1; part;
1199 part = part->mp_next, partnum++) {
1200 CT p = part->mp_part;
1202 sprintf(pp, "%d", partnum);
1203 p->c_partno = getcpy(partnam);
1204 if (compose_content(p) == NOTOK)
1209 ** If the -rfc934mode switch is given, then check all
1210 ** the subparts of a multipart/digest. If they are all
1211 ** message/rfc822, then mark this content and all
1212 ** subparts with the rfc934 compatibility mode flag.
1214 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
1217 for (part = m->mp_parts; part; part = part->mp_next) {
1218 CT p = part->mp_part;
1220 if (p->c_subtype != MESSAGE_RFC822) {
1225 ct->c_rfc934 = is934;
1226 for (part = m->mp_parts; part; part = part->mp_next) {
1227 CT p = part->mp_part;
1229 if ((p->c_rfc934 = is934))
1235 ct->c_end = (partnum = strlen(prefix) + 2) + 2;
1239 for (part = m->mp_parts; part; part = part->mp_next)
1240 ct->c_end += part->mp_part->c_end + partnum;
1246 /* Nothing to do for type message */
1250 ** Discrete types (text/application/audio/image/video)
1255 int xstdout, len, buflen;
1256 char *bp, **ap, *cp;
1257 char *vec[4], buffer[BUFSIZ];
1259 CI ci = &ct->c_ctinfo;
1262 if (!(cp = ci->ci_magic))
1263 adios(NULL, "internal error(5)");
1265 tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1266 if (tfile == NULL) {
1267 adios("mhbuild", "unable to create temporary file");
1269 ce->ce_file = getcpy(tfile);
1274 /* Get buffer ready to go */
1277 buflen = sizeof(buffer);
1280 ** Parse composition string into buffer
1282 for ( ; *cp; cp++) {
1288 ** insert parameters from
1294 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1295 snprintf(bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1305 /* %f, and stdout is not-redirected */
1311 ** insert temporary filename
1312 ** where content should be
1315 snprintf(bp, buflen, "%s", ce->ce_file);
1319 /* insert content subtype */
1320 strncpy(bp, ci->ci_subtype, buflen);
1324 /* insert character % */
1345 printf("composing content %s/%s from command\n\t%s\n", ci->ci_type, ci->ci_subtype, buffer);
1347 fflush(stdout); /* not sure if need for -noverbose */
1354 if ((out = fopen(ce->ce_file, "w")) == NULL)
1355 adios(ce->ce_file, "unable to open for writing");
1357 switch (child_id = fork()) {
1359 adios("fork", "unable to fork");
1364 dup2(fileno(out), 1);
1366 execvp("/bin/sh", vec);
1367 fprintf(stderr, "unable to exec ");
1374 if (pidXwait(child_id, NULL))
1380 /* Check size of file */
1381 if (listsw && ct->c_end == 0L) {
1384 if (stat(ce->ce_file, &st) != NOTOK)
1385 ct->c_end = (long) st.st_size;
1395 ** Scan the content.
1397 ** 1) choose a transfer encoding.
1398 ** 2) check for clashes with multipart boundary string.
1399 ** 3) for text content, figure out which character set is being used.
1401 ** If there is a clash with one of the contents and the multipart boundary,
1402 ** this function will exit with NOTOK. This will cause the scanning process
1403 ** to be repeated with a different multipart boundary. It is possible
1404 ** (although highly unlikely) that this scan will be repeated multiple times.
1411 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1412 int checklinelen = 0, linelen = 0; /* check for long lines */
1413 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1414 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1415 int checkebcdic = 0, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
1416 unsigned char *cp = NULL, buffer[BUFSIZ];
1417 struct text *t = NULL;
1419 CE ce = ct->c_cefile;
1422 ** handle multipart by scanning all subparts
1423 ** and then checking their encoding.
1425 if (ct->c_type == CT_MULTIPART) {
1426 struct multipart *m = (struct multipart *) ct->c_ctparams;
1429 /* initially mark the domain of enclosing multipart as 7bit */
1430 ct->c_encoding = CE_7BIT;
1432 for (part = m->mp_parts; part; part = part->mp_next) {
1433 CT p = part->mp_part;
1435 if (scan_content(p) == NOTOK) {
1436 /* choose encoding for subpart */
1441 ** if necessary, enlarge encoding for enclosing
1444 if (p->c_encoding == CE_BINARY)
1445 ct->c_encoding = CE_BINARY;
1446 if (p->c_encoding == CE_8BIT &&
1447 ct->c_encoding != CE_BINARY)
1448 ct->c_encoding = CE_8BIT;
1455 ** Decide what to check while scanning this content.
1457 switch (ct->c_type) {
1461 if (ct->c_subtype == TEXT_PLAIN) {
1466 checkebcdic = ebcdicsw;
1472 case CT_APPLICATION:
1474 checkebcdic = ebcdicsw;
1492 ** Don't check anything for these types,
1493 ** since we are forcing use of base64.
1504 ** Scan the unencoded content
1506 if (check8bit || checklinelen || checklinespace || checkboundary) {
1507 if ((in = fopen(ce->ce_file, "r")) == NULL)
1508 adios(ce->ce_file, "unable to open for reading");
1509 len = strlen(prefix);
1511 while (fgets(buffer, sizeof(buffer) - 1, in)) {
1513 ** Check for 8bit data.
1516 for (cp = buffer; *cp; cp++) {
1517 if (!isascii(*cp)) {
1519 /* no need to keep checking */
1523 ** Check if character is ebcdic-safe.
1524 ** We only check this if also checking
1527 if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1529 /* no need to keep checking */
1536 ** Check line length.
1538 if (checklinelen && (strlen(buffer) > CPERLIN + 1)) {
1540 checklinelen = 0; /* no need to keep checking */
1544 ** Check if line ends with a space.
1546 if (checklinespace &&
1547 (cp = buffer + strlen(buffer) - 2) >
1548 buffer && isspace(*cp)) {
1550 /* no need to keep checking */
1555 ** Check if content contains a line that clashes
1556 ** with our standard boundary for multipart messages.
1558 if (checkboundary && buffer[0] == '-' &&
1560 for (cp = buffer + strlen(buffer) - 1;
1565 if (strncmp(buffer + 2, prefix, len)==0 &&
1566 isdigit(buffer[2 + len])) {
1568 /* no need to keep checking */
1577 ** Decide which transfer encoding to use.
1579 switch (ct->c_type) {
1582 ** If the text content didn't specify a character
1583 ** set, we need to figure out which one was used.
1585 t = (struct text *) ct->c_ctparams;
1586 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1587 CI ci = &ct->c_ctinfo;
1590 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1594 t->tx_charset = CHARSET_UNKNOWN;
1595 *ap = concat("charset=", write_charset_8bit(),
1598 t->tx_charset = CHARSET_USASCII;
1599 *ap = getcpy("charset=us-ascii");
1602 cp = strchr(*ap++, '=');
1608 if (contains8bit || ebcdicunsafe || linelen || linespace)
1609 ct->c_encoding = CE_QUOTED;
1611 ct->c_encoding = CE_7BIT;
1614 case CT_APPLICATION:
1615 /* For application type, use base64, except when postscript */
1616 if (contains8bit || ebcdicunsafe || linelen || linespace)
1617 ct->c_encoding = (ct->c_subtype ==
1618 APPLICATION_POSTSCRIPT) ?
1619 CE_QUOTED : CE_BASE64;
1621 ct->c_encoding = CE_7BIT;
1625 ct->c_encoding = CE_7BIT;
1631 /* For audio, image, and video contents, just use base64 */
1632 ct->c_encoding = CE_BASE64;
1636 return (boundaryclash ? NOTOK : OK);
1641 ** Scan the content structures, and build header
1642 ** fields that will need to be output into the
1647 build_headers(CT ct)
1651 char *np, *vp, buffer[BUFSIZ];
1652 CI ci = &ct->c_ctinfo;
1655 ** If message is type multipart, then add the multipart
1656 ** boundary to the list of attribute/value pairs.
1658 if (ct->c_type == CT_MULTIPART) {
1660 static int level = 0; /* store nesting level */
1664 snprintf(buffer, sizeof(buffer), "boundary=%s%d",
1666 cp = strchr(*ap++ = getcpy(buffer), '=');
1673 ** Skip the output of Content-Type, parameters, content
1674 ** description and disposition, and Content-ID if the
1675 ** content is of type "message" and the rfc934 compatibility
1676 ** flag is set (which means we are inside multipart/digest
1677 ** and the switch -rfc934mode was given).
1679 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1683 ** output the content type and subtype
1685 np = getcpy(TYPE_FIELD);
1686 vp = concat(" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1688 /* keep track of length of line */
1689 len = strlen(TYPE_FIELD) + strlen(ci->ci_type) +
1690 strlen(ci->ci_subtype) + 3;
1693 ** Append the attribute/value pairs to
1694 ** the end of the Content-Type line.
1696 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1700 snprintf(buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1701 if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) {
1702 vp = add("\n\t", vp);
1708 vp = add(buffer, vp);
1713 ** Append any RFC-822 comment to the end of
1714 ** the Content-Type line.
1716 if (ci->ci_comment) {
1717 snprintf(buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1718 if (len + 1 + (cc = 2 + strlen(ci->ci_comment)) >= CPERLIN) {
1719 vp = add("\n\t", vp);
1725 vp = add(buffer, vp);
1729 add_header(ct, np, vp);
1732 ** output the Content-ID, unless disabled by -nocontentid
1734 if (contentidsw && ct->c_id) {
1735 np = getcpy(ID_FIELD);
1736 vp = concat(" ", ct->c_id, NULL);
1737 add_header(ct, np, vp);
1741 ** output the Content-Description
1744 np = getcpy(DESCR_FIELD);
1745 vp = concat(" ", ct->c_descr, NULL);
1746 add_header(ct, np, vp);
1750 ** output the Content-Disposition
1753 np = getcpy(DISPO_FIELD);
1754 vp = concat(" ", ct->c_dispo, NULL);
1755 add_header(ct, np, vp);
1760 ** output the Content-Transfer-Encoding
1762 switch (ct->c_encoding) {
1764 /* Nothing to output */
1768 if (ct->c_type == CT_MESSAGE)
1769 adios(NULL, "internal error, invalid encoding");
1771 np = getcpy(ENCODING_FIELD);
1772 vp = concat(" ", "8bit", "\n", NULL);
1773 add_header(ct, np, vp);
1777 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1778 adios(NULL, "internal error, invalid encoding");
1780 np = getcpy(ENCODING_FIELD);
1781 vp = concat(" ", "quoted-printable", "\n", NULL);
1782 add_header(ct, np, vp);
1786 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1787 adios(NULL, "internal error, invalid encoding");
1789 np = getcpy(ENCODING_FIELD);
1790 vp = concat(" ", "base64", "\n", NULL);
1791 add_header(ct, np, vp);
1795 if (ct->c_type == CT_MESSAGE)
1796 adios(NULL, "internal error, invalid encoding");
1798 np = getcpy(ENCODING_FIELD);
1799 vp = concat(" ", "binary", "\n", NULL);
1800 add_header(ct, np, vp);
1804 adios(NULL, "unknown transfer encoding in content");
1809 ** Additional content specific header processing
1811 switch (ct->c_type) {
1814 struct multipart *m;
1817 m = (struct multipart *) ct->c_ctparams;
1818 for (part = m->mp_parts; part; part = part->mp_next) {