3 * mhbuildsbr.c -- routines to expand/translate MIME composition files
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
11 * This code was originally part of mhn.c. I split it into
12 * a separate program (mhbuild.c) and then later split it
13 * again (mhbuildsbr.c). But the code still has some of
14 * the mhn.c code in it. This program needs additional
15 * streamlining and removal of unneeded code.
20 #include <h/signals.h>
27 #include <h/mhparse.h>
30 #ifdef TIME_WITH_SYS_TIME
31 # include <sys/time.h>
34 # ifdef TM_IN_SYS_TIME
35 # include <sys/time.h>
48 extern int contentidsw;
50 extern int endian; /* mhmisc.c */
53 extern int rcachesw; /* mhcachesbr.c */
54 extern int wcachesw; /* mhcachesbr.c */
57 * Directory to place tmp files. This must
58 * be set before these routines are called.
64 static char prefix[] = "----- =_aaaaaaaaaa";
68 int make_intermediates (char *);
69 void content_error (char *, CT, char *, ...);
72 int find_cache (CT, int, int *, char *, char *, int);
75 int ftp_get (char *, char *, char *, char *, char *, char *, int, int);
78 void free_content (CT);
79 void free_ctinfo (CT);
80 void free_encoding (CT, int);
85 CT build_mime (char *);
90 static int init_decoded_content (CT);
91 static char *fgetstr (char *, int, FILE *);
92 static int user_content (FILE *, char *, char *, CT *);
93 static void set_id (CT, int);
94 static int compose_content (CT);
95 static int scan_content (CT);
96 static int build_headers (CT);
97 static char *calculate_digest (CT, int);
101 * Main routine for translating composition file
102 * into valid MIME message. It translates the draft
103 * into a content structure (actually a tree of content
104 * structures). This message then can be manipulated
105 * in various ways, including being output via
110 build_mime (char *infile)
113 char buf[BUFSIZ], name[NAMESZ];
120 umask (~m_gmprot ());
122 /* open the composition draft */
123 if ((in = fopen (infile, "r")) == NULL)
124 adios (infile, "unable to open for reading");
127 * Allocate space for primary (outside) content
129 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
130 adios (NULL, "out of memory");
133 * Allocate structure for handling decoded content
134 * for this part. We don't really need this, but
135 * allocate it to remain consistent.
137 init_decoded_content (ct);
140 * Parse some of the header fields in the composition
141 * draft into the linked list of header fields for
142 * the new MIME message.
144 for (compnum = 1, state = FLD;;) {
145 switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
151 /* abort if draft has Mime-Version header field */
152 if (!mh_strcasecmp (name, VRSN_FIELD))
153 adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
155 /* abort if draft has Content-Transfer-Encoding header field */
156 if (!mh_strcasecmp (name, ENCODING_FIELD))
157 adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
159 /* ignore any Content-Type fields in the header */
160 if (!mh_strcasecmp (name, TYPE_FIELD)) {
161 while (state == FLDPLUS)
162 state = m_getfld (state, name, buf, sizeof(buf), in);
166 /* get copies of the buffers */
167 np = add (name, NULL);
168 vp = add (buf, NULL);
170 /* if necessary, get rest of field */
171 while (state == FLDPLUS) {
172 state = m_getfld (state, name, buf, sizeof(buf), in);
173 vp = add (buf, vp); /* add to previous value */
176 /* Now add the header data to the list */
177 add_header (ct, np, vp);
180 /* if this wasn't the last header field, then continue */
186 adios (NULL, "draft has empty body -- no directives!");
191 fseek (in, (long) (-strlen (buf)), SEEK_CUR);
196 adios (NULL, "message format error in component #%d", compnum);
199 adios (NULL, "getfld() returned %d", state);
205 * Now add the MIME-Version header field
206 * to the list of header fields.
208 np = add (VRSN_FIELD, NULL);
209 vp = concat (" ", VRSN_VALUE, "\n", NULL);
210 add_header (ct, np, vp);
213 * We initally assume we will find multiple contents in the
214 * draft. So create a multipart/mixed content to hold everything.
215 * We can remove this later, if it is not needed.
217 if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
219 ct->c_type = CT_MULTIPART;
220 ct->c_subtype = MULTI_MIXED;
221 ct->c_file = add (infile, NULL);
223 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
224 adios (NULL, "out of memory");
225 ct->c_ctparams = (void *) m;
229 * read and parse the composition file
230 * and the directives it contains.
232 while (fgetstr (buf, sizeof(buf) - 1, in)) {
236 if (user_content (in, infile, buf, &p) == DONE) {
237 admonish (NULL, "ignoring spurious #end");
243 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
244 adios (NULL, "out of memory");
251 * close the composition draft since
252 * it's not needed any longer.
256 /* check if any contents were found */
258 adios (NULL, "no content directives found");
261 * If only one content was found, then remove and
262 * free the outer multipart content.
264 if (!m->mp_parts->mp_next) {
267 p = m->mp_parts->mp_part;
268 m->mp_parts->mp_part = NULL;
270 /* move header fields */
271 p->c_first_hf = ct->c_first_hf;
272 p->c_last_hf = ct->c_last_hf;
273 ct->c_first_hf = NULL;
274 ct->c_last_hf = NULL;
283 * Fill out, or expand directives. Parse and execute
284 * commands specified by profile composition strings.
286 compose_content (ct);
288 if ((cp = strchr(prefix, 'a')) == NULL)
289 adios (NULL, "internal error(4)");
292 * Scan the contents. Choose a transfer encoding, and
293 * check if prefix for multipart boundary clashes with
294 * any of the contents.
296 while (scan_content (ct) == NOTOK) {
301 adios (NULL, "giving up trying to find a unique delimiter string");
307 /* Build the rest of the header field structures */
315 * Set up structures for placing unencoded
316 * content when building parts.
320 init_decoded_content (CT ct)
324 if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
325 adios (NULL, "out of memory");
328 ct->c_ceopenfnx = open7Bit; /* since unencoded */
329 ct->c_ceclosefnx = close_encoding;
330 ct->c_cesizefnx = NULL; /* since unencoded */
337 fgetstr (char *s, int n, FILE *stream)
341 for (ep = (cp = s) + n; cp < ep; ) {
344 if (!fgets (cp, n, stream))
345 return (cp != s ? s : NULL);
346 if (cp == s && *cp != '#')
349 cp += (i = strlen (cp)) - 1;
350 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
361 * Parse the composition draft for text and directives.
362 * Do initial setup of Content structure.
366 user_content (FILE *in, char *file, char *buf, CT *ctp)
375 struct str2init *s2i;
380 if (buf[0] == '\n' || strcmp (buf, "#\n") == 0) {
385 /* allocate basic Content structure */
386 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
387 adios (NULL, "out of memory");
390 /* allocate basic structure for handling decoded content */
391 init_decoded_content (ct);
398 * Handle inline text. Check if line
399 * is one of the following forms:
401 * 1) doesn't begin with '#' (implicit directive)
402 * 2) begins with "##" (implicit directive)
403 * 3) begins with "#<"
405 if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
409 char content[BUFSIZ];
413 cp = m_mktemp2(NULL, invo_name, NULL, &out);
414 if (cp == NULL) adios("mhbuildsbr", "unable to create temporary file");
416 /* use a temp file to collect the plain text lines */
417 ce->ce_file = add (cp, NULL);
420 if (buf[0] == '#' && buf[1] == '<') {
421 strncpy (content, buf + 2, sizeof(content));
428 /* the directive is implicit */
429 strncpy (content, "text/plain", sizeof(content));
431 strncpy (buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
435 if (headers >= 0 && uprf (buffer, DESCR_FIELD)
436 && buffer[i = strlen (DESCR_FIELD)] == ':') {
440 ct->c_descr = add (buffer + i + 1, ct->c_descr);
441 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
442 adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
450 adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
458 if (headers >= 0 && uprf (buffer, DISPO_FIELD)
459 && buffer[i = strlen (DISPO_FIELD)] == ':') {
463 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
464 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
465 adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
473 adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
481 if (headers != 1 || buffer[0] != '\n')
487 if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
489 if (buffer[0] == '#') {
492 if (buffer[1] != '#')
494 for (cp = (bp = buffer) + 1; *cp; cp++)
501 ct->c_end = ftell (out);
504 /* parse content type */
505 if (get_ctinfo (content, ct, inlineD) == NOTOK)
508 for (s2i = str2cts; s2i->si_key; s2i++)
509 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
511 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
515 * check type specified (possibly implicitly)
517 switch (ct->c_type = s2i->si_val) {
519 if (!mh_strcasecmp (ci->ci_subtype, "rfc822")) {
520 ct->c_encoding = CE_7BIT;
525 adios (NULL, "it doesn't make sense to define an in-line %s content",
526 ct->c_type == CT_MESSAGE ? "message" : "multipart");
531 if ((ct->c_ctinitfnx = s2i->si_init))
532 (*ct->c_ctinitfnx) (ct);
537 fseek (in, pos, SEEK_SET);
542 * If we've reached this point, the next line
543 * must be some type of explicit directive.
546 /* check if directive is external-type */
547 extrnal = (buf[1] == '@');
549 /* parse directive */
550 if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
553 /* check directive against the list of MIME types */
554 for (s2i = str2cts; s2i->si_key; s2i++)
555 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
559 * Check if the directive specified a valid type.
560 * This will happen if it was one of the following forms:
567 adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
569 switch (ct->c_type = s2i->si_val) {
571 adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
572 ci->ci_type, ci->ci_subtype);
576 if (!mh_strcasecmp (ci->ci_subtype, "partial"))
577 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
578 ci->ci_type, ci->ci_subtype);
579 if (!mh_strcasecmp (ci->ci_subtype, "external-body"))
580 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
581 ci->ci_type, ci->ci_subtype);
584 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
585 ci->ci_type, ci->ci_subtype);
589 if ((ct->c_ctinitfnx = s2i->si_init))
590 (*ct->c_ctinitfnx) (ct);
595 * #@type/subtype (external types directive)
602 adios (NULL, "need external information for \"#@%s/%s\"",
603 ci->ci_type, ci->ci_subtype);
606 snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
611 * Since we are using the current Content structure to
612 * hold information about the type of the external
613 * reference, we need to create another Content structure
614 * for the message/external-body to wrap it in.
616 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
617 adios (NULL, "out of memory");
620 if (get_ctinfo (buffer, ct, 0) == NOTOK)
622 ct->c_type = CT_MESSAGE;
623 ct->c_subtype = MESSAGE_EXTERNAL;
625 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
626 adios (NULL, "out of memory");
627 ct->c_ctparams = (void *) e;
633 if (params_external (ct, 1) == NOTOK)
639 /* Handle [file] argument */
641 /* check if specifies command to execute */
642 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
643 for (cp = ci->ci_magic + 1; isspace (*cp); cp++)
646 adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
651 /* record filename of decoded contents */
652 ce->ce_file = ci->ci_magic;
653 if (access (ce->ce_file, R_OK) == NOTOK)
654 adios ("reading", "unable to access %s for", ce->ce_file);
655 if (listsw && stat (ce->ce_file, &st) != NOTOK)
656 ct->c_end = (long) st.st_size;
663 * No [file] argument, so check profile for
664 * method to compose content.
666 snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
667 invo_name, ci->ci_type, ci->ci_subtype);
668 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
669 snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type);
670 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
671 content_error (NULL, ct, "don't know how to compose content");
675 ci->ci_magic = add (cp, NULL);
680 adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
684 * #forw [+folder] [msgs]
686 if (!mh_strcasecmp (ci->ci_type, "forw")) {
688 char *folder, *arguments[MAXARGS];
692 ap = brkstring (ci->ci_magic, " ", "\n");
693 copyip (ap, arguments, MAXARGS);
695 arguments[0] = "cur";
700 /* search the arguments for a folder name */
701 for (ap = arguments; *ap; ap++) {
703 if (*cp == '+' || *cp == '@') {
705 adios (NULL, "only one folder per #forw directive");
707 folder = pluspath (cp);
711 /* else, use the current folder */
713 folder = add (getfolder (1), NULL);
715 if (!(mp = folder_read (folder)))
716 adios (NULL, "unable to read folder %s", folder);
717 for (ap = arguments; *ap; ap++) {
719 if (*cp != '+' && *cp != '@')
720 if (!m_convert (mp, cp))
727 * If there is more than one message to include, make this
728 * a content of type "multipart/digest" and insert each message
729 * as a subpart. If there is only one message, then make this
730 * a content of type "message/rfc822".
732 if (mp->numsel > 1) {
733 /* we are forwarding multiple messages */
734 if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
736 ct->c_type = CT_MULTIPART;
737 ct->c_subtype = MULTI_DIGEST;
739 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
740 adios (NULL, "out of memory");
741 ct->c_ctparams = (void *) m;
744 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
745 if (is_selected(mp, msgnum)) {
750 if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
751 adios (NULL, "out of memory");
752 init_decoded_content (p);
754 if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
756 p->c_type = CT_MESSAGE;
757 p->c_subtype = MESSAGE_RFC822;
759 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
760 pe->ce_file = add (buffer, NULL);
761 if (listsw && stat (pe->ce_file, &st) != NOTOK)
762 p->c_end = (long) st.st_size;
764 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
765 adios (NULL, "out of memory");
772 /* we are forwarding one message */
773 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
775 ct->c_type = CT_MESSAGE;
776 ct->c_subtype = MESSAGE_RFC822;
779 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
780 ce->ce_file = add (buffer, NULL);
781 if (listsw && stat (ce->ce_file, &st) != NOTOK)
782 ct->c_end = (long) st.st_size;
785 folder_free (mp); /* free folder/message structure */
792 if (!mh_strcasecmp (ci->ci_type, "end")) {
799 * #begin [ alternative | parallel ]
801 if (!mh_strcasecmp (ci->ci_type, "begin")) {
804 cp = SubMultiPart[vrsn - 1].kv_key;
805 } else if (!mh_strcasecmp (ci->ci_magic, "alternative")) {
806 vrsn = MULTI_ALTERNATE;
807 cp = SubMultiPart[vrsn - 1].kv_key;
808 } else if (!mh_strcasecmp (ci->ci_magic, "parallel")) {
809 vrsn = MULTI_PARALLEL;
810 cp = SubMultiPart[vrsn - 1].kv_key;
811 } else if (uprf (ci->ci_magic, "digest")) {
814 vrsn = MULTI_UNKNOWN;
819 snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
820 if (get_ctinfo (buffer, ct, 0) == NOTOK)
822 ct->c_type = CT_MULTIPART;
823 ct->c_subtype = vrsn;
825 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
826 adios (NULL, "out of memory");
827 ct->c_ctparams = (void *) m;
830 while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
834 if (user_content (in, file, buffer, &p) == DONE) {
836 adios (NULL, "empty \"#begin ... #end\" sequence");
842 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
843 adios (NULL, "out of memory");
848 admonish (NULL, "premature end-of-file, missing #end");
855 adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
856 return NOTOK; /* NOT REACHED */
861 set_id (CT ct, int top)
865 static time_t clock = 0;
870 snprintf (msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
871 (int) getpid(), (long) clock, LocalName());
873 msgfmt = getcpy(msgid);
875 snprintf (msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
876 ct->c_id = getcpy (msgid);
880 static char ebcdicsafe[0x100] = {
881 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
882 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
883 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
884 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
885 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
886 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
887 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
888 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
889 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
890 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
891 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
892 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
893 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
894 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
895 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
896 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
897 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
898 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
899 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
900 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
901 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
909 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
910 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
911 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
917 * Fill out, or expand the various contents in the composition
918 * draft. Read-in any necessary files. Parse and execute any
919 * commands specified by profile composition strings.
923 compose_content (CT ct)
925 CE ce = ct->c_cefile;
927 switch (ct->c_type) {
932 char partnam[BUFSIZ];
933 struct multipart *m = (struct multipart *) ct->c_ctparams;
937 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
938 pp = partnam + strlen (partnam);
943 /* first, we call compose_content on all the subparts */
944 for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
945 CT p = part->mp_part;
947 sprintf (pp, "%d", partnum);
948 p->c_partno = add (partnam, NULL);
949 if (compose_content (p) == NOTOK)
954 * If the -rfc934mode switch is given, then check all
955 * the subparts of a multipart/digest. If they are all
956 * message/rfc822, then mark this content and all
957 * subparts with the rfc934 compatibility mode flag.
959 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
962 for (part = m->mp_parts; part; part = part->mp_next) {
963 CT p = part->mp_part;
965 if (p->c_subtype != MESSAGE_RFC822) {
970 ct->c_rfc934 = is934;
971 for (part = m->mp_parts; part; part = part->mp_next) {
972 CT p = part->mp_part;
974 if ((p->c_rfc934 = is934))
980 ct->c_end = (partnum = strlen (prefix) + 2) + 2;
984 for (part = m->mp_parts; part; part = part->mp_next)
985 ct->c_end += part->mp_part->c_end + partnum;
991 /* Nothing to do for type message */
995 * Discrete types (text/application/audio/image/video)
1000 int i, xstdout, len, buflen;
1001 char *bp, **ap, *cp;
1002 char *vec[4], buffer[BUFSIZ];
1004 CI ci = &ct->c_ctinfo;
1007 if (!(cp = ci->ci_magic))
1008 adios (NULL, "internal error(5)");
1010 tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1011 if (tfile == NULL) {
1012 adios("mhbuildsbr", "unable to create temporary file");
1014 ce->ce_file = add (tfile, NULL);
1019 /* Get buffer ready to go */
1022 buflen = sizeof(buffer);
1025 * Parse composition string into buffer
1027 for ( ; *cp; cp++) {
1032 /* insert parameters from directive */
1036 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1037 snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1047 /* %f, and stdout is not-redirected */
1053 * insert temporary filename where
1054 * content should be written
1056 snprintf (bp, buflen, "%s", ce->ce_file);
1060 /* insert content subtype */
1061 strncpy (bp, ci->ci_subtype, buflen);
1065 /* insert character % */
1086 printf ("composing content %s/%s from command\n\t%s\n",
1087 ci->ci_type, ci->ci_subtype, buffer);
1089 fflush (stdout); /* not sure if need for -noverbose */
1096 if ((out = fopen (ce->ce_file, "w")) == NULL)
1097 adios (ce->ce_file, "unable to open for writing");
1099 for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++)
1103 adios ("fork", "unable to fork");
1108 dup2 (fileno (out), 1);
1109 close (fileno (out));
1110 execvp ("/bin/sh", vec);
1111 fprintf (stderr, "unable to exec ");
1118 if (pidXwait(child_id, NULL))
1124 /* Check size of file */
1125 if (listsw && ct->c_end == 0L) {
1128 if (stat (ce->ce_file, &st) != NOTOK)
1129 ct->c_end = (long) st.st_size;
1141 * 1) choose a transfer encoding.
1142 * 2) check for clashes with multipart boundary string.
1143 * 3) for text content, figure out which character set is being used.
1145 * If there is a clash with one of the contents and the multipart boundary,
1146 * this function will exit with NOTOK. This will cause the scanning process
1147 * to be repeated with a different multipart boundary. It is possible
1148 * (although highly unlikely) that this scan will be repeated multiple times.
1152 scan_content (CT ct)
1155 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1156 int checklinelen = 0, linelen = 0; /* check for long lines */
1157 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1158 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1159 int checkebcdic = 0, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
1160 unsigned char *cp = NULL, buffer[BUFSIZ];
1161 struct text *t = NULL;
1163 CE ce = ct->c_cefile;
1166 * handle multipart by scanning all subparts
1167 * and then checking their encoding.
1169 if (ct->c_type == CT_MULTIPART) {
1170 struct multipart *m = (struct multipart *) ct->c_ctparams;
1173 /* initially mark the domain of enclosing multipart as 7bit */
1174 ct->c_encoding = CE_7BIT;
1176 for (part = m->mp_parts; part; part = part->mp_next) {
1177 CT p = part->mp_part;
1179 if (scan_content (p) == NOTOK) /* choose encoding for subpart */
1182 /* if necessary, enlarge encoding for enclosing multipart */
1183 if (p->c_encoding == CE_BINARY)
1184 ct->c_encoding = CE_BINARY;
1185 if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
1186 ct->c_encoding = CE_8BIT;
1193 * Decide what to check while scanning this content.
1195 switch (ct->c_type) {
1199 if (ct->c_subtype == TEXT_PLAIN) {
1204 checkebcdic = ebcdicsw;
1210 case CT_APPLICATION:
1212 checkebcdic = ebcdicsw;
1224 /* don't check anything for message/external */
1225 if (ct->c_subtype == MESSAGE_EXTERNAL)
1235 * Don't check anything for these types,
1236 * since we are forcing use of base64.
1247 * Scan the unencoded content
1249 if (check8bit || checklinelen || checklinespace || checkboundary) {
1250 if ((in = fopen (ce->ce_file, "r")) == NULL)
1251 adios (ce->ce_file, "unable to open for reading");
1252 len = strlen (prefix);
1254 while (fgets (buffer, sizeof(buffer) - 1, in)) {
1256 * Check for 8bit data.
1259 for (cp = buffer; *cp; cp++) {
1260 if (!isascii (*cp)) {
1262 check8bit = 0; /* no need to keep checking */
1265 * Check if character is ebcdic-safe. We only check
1266 * this if also checking for 8bit data.
1268 if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1270 checkebcdic = 0; /* no need to keep checking */
1276 * Check line length.
1278 if (checklinelen && (strlen (buffer) > CPERLIN + 1)) {
1280 checklinelen = 0; /* no need to keep checking */
1284 * Check if line ends with a space.
1286 if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace (*cp)) {
1288 checklinespace = 0; /* no need to keep checking */
1292 * Check if content contains a line that clashes
1293 * with our standard boundary for multipart messages.
1295 if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
1296 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
1300 if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) {
1302 checkboundary = 0; /* no need to keep checking */
1310 * Decide which transfer encoding to use.
1312 switch (ct->c_type) {
1315 * If the text content didn't specify a character
1316 * set, we need to figure out which one was used.
1318 t = (struct text *) ct->c_ctparams;
1319 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1320 CI ci = &ct->c_ctinfo;
1323 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1327 t->tx_charset = CHARSET_UNKNOWN;
1328 *ap = concat ("charset=", write_charset_8bit(), NULL);
1330 t->tx_charset = CHARSET_USASCII;
1331 *ap = add ("charset=us-ascii", NULL);
1334 cp = strchr(*ap++, '=');
1340 if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1341 ct->c_encoding = CE_QUOTED;
1343 ct->c_encoding = CE_7BIT;
1346 case CT_APPLICATION:
1347 /* For application type, use base64, except when postscript */
1348 if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1349 ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
1350 ? CE_QUOTED : CE_BASE64;
1352 ct->c_encoding = CE_7BIT;
1356 ct->c_encoding = CE_7BIT;
1362 /* For audio, image, and video contents, just use base64 */
1363 ct->c_encoding = CE_BASE64;
1367 return (boundaryclash ? NOTOK : OK);
1372 * Scan the content structures, and build header
1373 * fields that will need to be output into the
1378 build_headers (CT ct)
1380 int cc, mailbody, len;
1382 char *np, *vp, buffer[BUFSIZ];
1383 CI ci = &ct->c_ctinfo;
1386 * If message is type multipart, then add the multipart
1387 * boundary to the list of attribute/value pairs.
1389 if (ct->c_type == CT_MULTIPART) {
1391 static int level = 0; /* store nesting level */
1395 snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++);
1396 cp = strchr(*ap++ = add (buffer, NULL), '=');
1403 * Skip the output of Content-Type, parameters, content
1404 * description and disposition, and Content-ID if the
1405 * content is of type "message" and the rfc934 compatibility
1406 * flag is set (which means we are inside multipart/digest
1407 * and the switch -rfc934mode was given).
1409 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1413 * output the content type and subtype
1415 np = add (TYPE_FIELD, NULL);
1416 vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1418 /* keep track of length of line */
1419 len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
1420 + strlen (ci->ci_subtype) + 3;
1422 mailbody = ct->c_type == CT_MESSAGE
1423 && ct->c_subtype == MESSAGE_EXTERNAL
1424 && ((struct exbody *) ct->c_ctparams)->eb_body;
1427 * Append the attribute/value pairs to
1428 * the end of the Content-Type line.
1430 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1431 if (mailbody && !mh_strcasecmp (*ap, "body"))
1437 snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1438 if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
1439 vp = add ("\n\t", vp);
1445 vp = add (buffer, vp);
1450 * Append any RFC-822 comment to the end of
1451 * the Content-Type line.
1453 if (ci->ci_comment) {
1454 snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1455 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
1456 vp = add ("\n\t", vp);
1462 vp = add (buffer, vp);
1465 vp = add ("\n", vp);
1466 add_header (ct, np, vp);
1469 * output the Content-ID, unless disabled by -nocontentid
1471 if (contentidsw && ct->c_id) {
1472 np = add (ID_FIELD, NULL);
1473 vp = concat (" ", ct->c_id, NULL);
1474 add_header (ct, np, vp);
1478 * output the Content-Description
1481 np = add (DESCR_FIELD, NULL);
1482 vp = concat (" ", ct->c_descr, NULL);
1483 add_header (ct, np, vp);
1487 * output the Content-Disposition
1490 np = add (DISPO_FIELD, NULL);
1491 vp = concat (" ", ct->c_dispo, NULL);
1492 add_header (ct, np, vp);
1497 * If this is the internal content structure for a
1498 * "message/external", then we are done with the
1499 * headers (since it has no body).
1505 * output the Content-MD5
1508 np = add (MD5_FIELD, NULL);
1509 vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
1510 add_header (ct, np, vp);
1514 * output the Content-Transfer-Encoding
1516 switch (ct->c_encoding) {
1518 /* Nothing to output */
1520 np = add (ENCODING_FIELD, NULL);
1521 vp = concat (" ", "7bit", "\n", NULL);
1522 add_header (ct, np, vp);
1527 if (ct->c_type == CT_MESSAGE)
1528 adios (NULL, "internal error, invalid encoding");
1530 np = add (ENCODING_FIELD, NULL);
1531 vp = concat (" ", "8bit", "\n", NULL);
1532 add_header (ct, np, vp);
1536 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1537 adios (NULL, "internal error, invalid encoding");
1539 np = add (ENCODING_FIELD, NULL);
1540 vp = concat (" ", "quoted-printable", "\n", NULL);
1541 add_header (ct, np, vp);
1545 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1546 adios (NULL, "internal error, invalid encoding");
1548 np = add (ENCODING_FIELD, NULL);
1549 vp = concat (" ", "base64", "\n", NULL);
1550 add_header (ct, np, vp);
1554 if (ct->c_type == CT_MESSAGE)
1555 adios (NULL, "internal error, invalid encoding");
1557 np = add (ENCODING_FIELD, NULL);
1558 vp = concat (" ", "binary", "\n", NULL);
1559 add_header (ct, np, vp);
1563 adios (NULL, "unknown transfer encoding in content");
1568 * Additional content specific header processing
1570 switch (ct->c_type) {
1573 struct multipart *m;
1576 m = (struct multipart *) ct->c_ctparams;
1577 for (part = m->mp_parts; part; part = part->mp_next) {
1587 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1590 e = (struct exbody *) ct->c_ctparams;
1591 build_headers (e->eb_content);
1604 static char nib2b64[0x40+1] =
1605 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1608 calculate_digest (CT ct, int asciiP)
1611 char buffer[BUFSIZ], *vp, *op;
1613 unsigned char digest[16];
1614 unsigned char outbuf[25];
1617 CE ce = ct->c_cefile;
1620 if ((in = fopen (ce->ce_file, "r")) == NULL)
1621 adios (ce->ce_file, "unable to open for reading");
1623 /* Initialize md5 context */
1624 MD5Init (&mdContext);
1626 /* calculate md5 message digest */
1628 while (fgets (buffer, sizeof(buffer) - 1, in)) {
1631 cp = buffer + strlen (buffer) - 1;
1632 if ((c = *cp) == '\n')
1635 MD5Update (&mdContext, (unsigned char *) buffer,
1636 (unsigned int) strlen (buffer));
1639 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
1642 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
1643 MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
1646 /* md5 finalization. Write digest and zero md5 context */
1647 MD5Final (digest, &mdContext);
1652 /* print debugging info */
1656 fprintf (stderr, "MD5 digest=");
1657 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1659 fprintf (stderr, "%02x", *dp & 0xff);
1660 fprintf (stderr, "\n");
1663 /* encode the digest using base64 */
1664 for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
1665 cc > 0; cc -= 3, op += 4) {
1669 bits = (*dp++ & 0xff) << 16;
1671 bits |= (*dp++ & 0xff) << 8;
1673 bits |= *dp++ & 0xff;
1676 for (bp = op + 4; bp > op; bits >>= 6)
1677 *--bp = nib2b64[bits & 0x3f];
1685 /* null terminate string */
1688 /* now make copy and return string */
1689 vp = concat (" ", outbuf, "\n", NULL);