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 HAVE_SYS_TIME_H
31 # include <sys/time.h>
42 extern int contentidsw;
44 extern int endian; /* mhmisc.c */
47 extern int rcachesw; /* mhcachesbr.c */
48 extern int wcachesw; /* mhcachesbr.c */
51 * Directory to place tmp files. This must
52 * be set before these routines are called.
58 static char prefix[] = "----- =_aaaaaaaaaa";
62 int make_intermediates (char *);
63 void content_error (char *, CT, char *, ...);
66 int find_cache (CT, int, int *, char *, char *, int);
69 void free_content (CT);
70 void free_ctinfo (CT);
71 void free_encoding (CT, int);
76 CT build_mime (char *);
81 static int init_decoded_content (CT);
82 static char *fgetstr (char *, int, FILE *);
83 static int user_content (FILE *, char *, char *, CT *);
84 static void set_id (CT, int);
85 static int compose_content (CT);
86 static int scan_content (CT);
87 static int build_headers (CT);
88 static char *calculate_digest (CT, int);
92 * Main routine for translating composition file
93 * into valid MIME message. It translates the draft
94 * into a content structure (actually a tree of content
95 * structures). This message then can be manipulated
96 * in various ways, including being output via
101 build_mime (char *infile)
104 char buf[BUFSIZ], name[NAMESZ];
111 umask (~m_gmprot ());
113 /* open the composition draft */
114 if ((in = fopen (infile, "r")) == NULL)
115 adios (infile, "unable to open for reading");
118 * Allocate space for primary (outside) content
120 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
121 adios (NULL, "out of memory");
124 * Allocate structure for handling decoded content
125 * for this part. We don't really need this, but
126 * allocate it to remain consistent.
128 init_decoded_content (ct);
131 * Parse some of the header fields in the composition
132 * draft into the linked list of header fields for
133 * the new MIME message.
135 for (compnum = 1, state = FLD;;) {
136 switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
142 /* abort if draft has Mime-Version header field */
143 if (!mh_strcasecmp (name, VRSN_FIELD))
144 adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
146 /* abort if draft has Content-Transfer-Encoding header field */
147 if (!mh_strcasecmp (name, ENCODING_FIELD))
148 adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
150 /* ignore any Content-Type fields in the header */
151 if (!mh_strcasecmp (name, TYPE_FIELD)) {
152 while (state == FLDPLUS)
153 state = m_getfld (state, name, buf, sizeof(buf), in);
157 /* get copies of the buffers */
158 np = add (name, NULL);
159 vp = add (buf, NULL);
161 /* if necessary, get rest of field */
162 while (state == FLDPLUS) {
163 state = m_getfld (state, name, buf, sizeof(buf), in);
164 vp = add (buf, vp); /* add to previous value */
167 /* Now add the header data to the list */
168 add_header (ct, np, vp);
171 /* if this wasn't the last header field, then continue */
177 adios (NULL, "draft has empty body -- no directives!");
182 fseek (in, (long) (-strlen (buf)), SEEK_CUR);
187 adios (NULL, "message format error in component #%d", compnum);
190 adios (NULL, "getfld() returned %d", state);
196 * Now add the MIME-Version header field
197 * to the list of header fields.
199 np = add (VRSN_FIELD, NULL);
200 vp = concat (" ", VRSN_VALUE, "\n", NULL);
201 add_header (ct, np, vp);
204 * We initally assume we will find multiple contents in the
205 * draft. So create a multipart/mixed content to hold everything.
206 * We can remove this later, if it is not needed.
208 if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
210 ct->c_type = CT_MULTIPART;
211 ct->c_subtype = MULTI_MIXED;
212 ct->c_file = add (infile, NULL);
214 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
215 adios (NULL, "out of memory");
216 ct->c_ctparams = (void *) m;
220 * read and parse the composition file
221 * and the directives it contains.
223 while (fgetstr (buf, sizeof(buf) - 1, in)) {
227 if (user_content (in, infile, buf, &p) == DONE) {
228 admonish (NULL, "ignoring spurious #end");
234 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
235 adios (NULL, "out of memory");
242 * close the composition draft since
243 * it's not needed any longer.
247 /* check if any contents were found */
249 adios (NULL, "no content directives found");
252 * If only one content was found, then remove and
253 * free the outer multipart content.
255 if (!m->mp_parts->mp_next) {
258 p = m->mp_parts->mp_part;
259 m->mp_parts->mp_part = NULL;
261 /* move header fields */
262 p->c_first_hf = ct->c_first_hf;
263 p->c_last_hf = ct->c_last_hf;
264 ct->c_first_hf = NULL;
265 ct->c_last_hf = NULL;
274 * Fill out, or expand directives. Parse and execute
275 * commands specified by profile composition strings.
277 compose_content (ct);
279 if ((cp = strchr(prefix, 'a')) == NULL)
280 adios (NULL, "internal error(4)");
283 * Scan the contents. Choose a transfer encoding, and
284 * check if prefix for multipart boundary clashes with
285 * any of the contents.
287 while (scan_content (ct) == NOTOK) {
292 adios (NULL, "giving up trying to find a unique delimiter string");
298 /* Build the rest of the header field structures */
306 * Set up structures for placing unencoded
307 * content when building parts.
311 init_decoded_content (CT ct)
315 if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
316 adios (NULL, "out of memory");
319 ct->c_ceopenfnx = open7Bit; /* since unencoded */
320 ct->c_ceclosefnx = close_encoding;
321 ct->c_cesizefnx = NULL; /* since unencoded */
328 fgetstr (char *s, int n, FILE *stream)
332 for (ep = (cp = s) + n; cp < ep; ) {
335 if (!fgets (cp, n, stream))
336 return (cp != s ? s : NULL);
337 if (cp == s && *cp != '#')
340 cp += (i = strlen (cp)) - 1;
341 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
352 * Parse the composition draft for text and directives.
353 * Do initial setup of Content structure.
357 user_content (FILE *in, char *file, char *buf, CT *ctp)
366 struct str2init *s2i;
371 if (buf[0] == '\n' || strcmp (buf, "#\n") == 0) {
376 /* allocate basic Content structure */
377 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
378 adios (NULL, "out of memory");
381 /* allocate basic structure for handling decoded content */
382 init_decoded_content (ct);
389 * Handle inline text. Check if line
390 * is one of the following forms:
392 * 1) doesn't begin with '#' (implicit directive)
393 * 2) begins with "##" (implicit directive)
394 * 3) begins with "#<"
396 if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
400 char content[BUFSIZ];
404 cp = m_mktemp2(NULL, invo_name, NULL, &out);
405 if (cp == NULL) adios("mhbuildsbr", "unable to create temporary file");
407 /* use a temp file to collect the plain text lines */
408 ce->ce_file = add (cp, NULL);
411 if (buf[0] == '#' && buf[1] == '<') {
412 strncpy (content, buf + 2, sizeof(content));
419 /* the directive is implicit */
420 strncpy (content, "text/plain", sizeof(content));
422 strncpy (buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
426 if (headers >= 0 && uprf (buffer, DESCR_FIELD)
427 && buffer[i = strlen (DESCR_FIELD)] == ':') {
431 ct->c_descr = add (buffer + i + 1, ct->c_descr);
432 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
433 adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
441 adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
449 if (headers >= 0 && uprf (buffer, DISPO_FIELD)
450 && buffer[i = strlen (DISPO_FIELD)] == ':') {
454 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
455 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
456 adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
464 adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
472 if (headers != 1 || buffer[0] != '\n')
478 if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
480 if (buffer[0] == '#') {
483 if (buffer[1] != '#')
485 for (cp = (bp = buffer) + 1; *cp; cp++)
492 ct->c_end = ftell (out);
495 /* parse content type */
496 if (get_ctinfo (content, ct, inlineD) == NOTOK)
499 for (s2i = str2cts; s2i->si_key; s2i++)
500 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
502 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
506 * check type specified (possibly implicitly)
508 switch (ct->c_type = s2i->si_val) {
510 if (!mh_strcasecmp (ci->ci_subtype, "rfc822")) {
511 ct->c_encoding = CE_7BIT;
516 adios (NULL, "it doesn't make sense to define an in-line %s content",
517 ct->c_type == CT_MESSAGE ? "message" : "multipart");
522 if ((ct->c_ctinitfnx = s2i->si_init))
523 (*ct->c_ctinitfnx) (ct);
528 fseek (in, pos, SEEK_SET);
533 * If we've reached this point, the next line
534 * must be some type of explicit directive.
537 /* check if directive is external-type */
538 extrnal = (buf[1] == '@');
540 /* parse directive */
541 if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
544 /* check directive against the list of MIME types */
545 for (s2i = str2cts; s2i->si_key; s2i++)
546 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
550 * Check if the directive specified a valid type.
551 * This will happen if it was one of the following forms:
558 adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
560 switch (ct->c_type = s2i->si_val) {
562 adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
563 ci->ci_type, ci->ci_subtype);
567 if (!mh_strcasecmp (ci->ci_subtype, "partial"))
568 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
569 ci->ci_type, ci->ci_subtype);
570 if (!mh_strcasecmp (ci->ci_subtype, "external-body"))
571 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
572 ci->ci_type, ci->ci_subtype);
575 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
576 ci->ci_type, ci->ci_subtype);
580 if ((ct->c_ctinitfnx = s2i->si_init))
581 (*ct->c_ctinitfnx) (ct);
586 * #@type/subtype (external types directive)
593 adios (NULL, "need external information for \"#@%s/%s\"",
594 ci->ci_type, ci->ci_subtype);
597 snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
602 * Since we are using the current Content structure to
603 * hold information about the type of the external
604 * reference, we need to create another Content structure
605 * for the message/external-body to wrap it in.
607 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
608 adios (NULL, "out of memory");
611 if (get_ctinfo (buffer, ct, 0) == NOTOK)
613 ct->c_type = CT_MESSAGE;
614 ct->c_subtype = MESSAGE_EXTERNAL;
616 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
617 adios (NULL, "out of memory");
618 ct->c_ctparams = (void *) e;
624 if (params_external (ct, 1) == NOTOK)
630 /* Handle [file] argument */
632 /* check if specifies command to execute */
633 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
634 for (cp = ci->ci_magic + 1; isspace (*cp); cp++)
637 adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
642 /* record filename of decoded contents */
643 ce->ce_file = ci->ci_magic;
644 if (access (ce->ce_file, R_OK) == NOTOK)
645 adios ("reading", "unable to access %s for", ce->ce_file);
646 if (listsw && stat (ce->ce_file, &st) != NOTOK)
647 ct->c_end = (long) st.st_size;
654 * No [file] argument, so check profile for
655 * method to compose content.
657 snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
658 invo_name, ci->ci_type, ci->ci_subtype);
659 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
660 snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type);
661 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
662 content_error (NULL, ct, "don't know how to compose content");
666 ci->ci_magic = add (cp, NULL);
671 adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
675 * #forw [+folder] [msgs]
677 if (!mh_strcasecmp (ci->ci_type, "forw")) {
679 char *folder, *arguments[MAXARGS];
683 ap = brkstring (ci->ci_magic, " ", "\n");
684 copyip (ap, arguments, MAXARGS);
686 arguments[0] = "cur";
691 /* search the arguments for a folder name */
692 for (ap = arguments; *ap; ap++) {
694 if (*cp == '+' || *cp == '@') {
696 adios (NULL, "only one folder per #forw directive");
698 folder = pluspath (cp);
702 /* else, use the current folder */
704 folder = add (getfolder (1), NULL);
706 if (!(mp = folder_read (folder)))
707 adios (NULL, "unable to read folder %s", folder);
708 for (ap = arguments; *ap; ap++) {
710 if (*cp != '+' && *cp != '@')
711 if (!m_convert (mp, cp))
718 * If there is more than one message to include, make this
719 * a content of type "multipart/digest" and insert each message
720 * as a subpart. If there is only one message, then make this
721 * a content of type "message/rfc822".
723 if (mp->numsel > 1) {
724 /* we are forwarding multiple messages */
725 if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
727 ct->c_type = CT_MULTIPART;
728 ct->c_subtype = MULTI_DIGEST;
730 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
731 adios (NULL, "out of memory");
732 ct->c_ctparams = (void *) m;
735 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
736 if (is_selected(mp, msgnum)) {
741 if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
742 adios (NULL, "out of memory");
743 init_decoded_content (p);
745 if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
747 p->c_type = CT_MESSAGE;
748 p->c_subtype = MESSAGE_RFC822;
750 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
751 pe->ce_file = add (buffer, NULL);
752 if (listsw && stat (pe->ce_file, &st) != NOTOK)
753 p->c_end = (long) st.st_size;
755 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
756 adios (NULL, "out of memory");
763 /* we are forwarding one message */
764 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
766 ct->c_type = CT_MESSAGE;
767 ct->c_subtype = MESSAGE_RFC822;
770 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
771 ce->ce_file = add (buffer, NULL);
772 if (listsw && stat (ce->ce_file, &st) != NOTOK)
773 ct->c_end = (long) st.st_size;
776 folder_free (mp); /* free folder/message structure */
783 if (!mh_strcasecmp (ci->ci_type, "end")) {
790 * #begin [ alternative | parallel ]
792 if (!mh_strcasecmp (ci->ci_type, "begin")) {
795 cp = SubMultiPart[vrsn - 1].kv_key;
796 } else if (!mh_strcasecmp (ci->ci_magic, "alternative")) {
797 vrsn = MULTI_ALTERNATE;
798 cp = SubMultiPart[vrsn - 1].kv_key;
799 } else if (!mh_strcasecmp (ci->ci_magic, "parallel")) {
800 vrsn = MULTI_PARALLEL;
801 cp = SubMultiPart[vrsn - 1].kv_key;
802 } else if (uprf (ci->ci_magic, "digest")) {
805 vrsn = MULTI_UNKNOWN;
810 snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
811 if (get_ctinfo (buffer, ct, 0) == NOTOK)
813 ct->c_type = CT_MULTIPART;
814 ct->c_subtype = vrsn;
816 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
817 adios (NULL, "out of memory");
818 ct->c_ctparams = (void *) m;
821 while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
825 if (user_content (in, file, buffer, &p) == DONE) {
827 adios (NULL, "empty \"#begin ... #end\" sequence");
833 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
834 adios (NULL, "out of memory");
839 admonish (NULL, "premature end-of-file, missing #end");
846 adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
847 return NOTOK; /* NOT REACHED */
852 set_id (CT ct, int top)
856 static time_t clock = 0;
861 snprintf (msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
862 (int) getpid(), (long) clock, LocalName(1));
864 msgfmt = getcpy(msgid);
866 snprintf (msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
867 ct->c_id = getcpy (msgid);
871 static char ebcdicsafe[0x100] = {
872 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
873 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
874 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
875 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
876 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
877 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
878 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
879 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
880 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
881 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
882 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
883 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
884 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
885 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
886 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
887 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
888 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
889 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
890 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
891 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
892 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
893 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
894 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
895 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
896 0x00, 0x00, 0x00, 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
908 * Fill out, or expand the various contents in the composition
909 * draft. Read-in any necessary files. Parse and execute any
910 * commands specified by profile composition strings.
914 compose_content (CT ct)
916 CE ce = ct->c_cefile;
918 switch (ct->c_type) {
923 char partnam[BUFSIZ];
924 struct multipart *m = (struct multipart *) ct->c_ctparams;
928 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
929 pp = partnam + strlen (partnam);
934 /* first, we call compose_content on all the subparts */
935 for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
936 CT p = part->mp_part;
938 sprintf (pp, "%d", partnum);
939 p->c_partno = add (partnam, NULL);
940 if (compose_content (p) == NOTOK)
945 * If the -rfc934mode switch is given, then check all
946 * the subparts of a multipart/digest. If they are all
947 * message/rfc822, then mark this content and all
948 * subparts with the rfc934 compatibility mode flag.
950 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
953 for (part = m->mp_parts; part; part = part->mp_next) {
954 CT p = part->mp_part;
956 if (p->c_subtype != MESSAGE_RFC822) {
961 ct->c_rfc934 = is934;
962 for (part = m->mp_parts; part; part = part->mp_next) {
963 CT p = part->mp_part;
965 if ((p->c_rfc934 = is934))
971 ct->c_end = (partnum = strlen (prefix) + 2) + 2;
975 for (part = m->mp_parts; part; part = part->mp_next)
976 ct->c_end += part->mp_part->c_end + partnum;
982 /* Nothing to do for type message */
986 * Discrete types (text/application/audio/image/video)
991 int i, xstdout, len, buflen;
993 char *vec[4], buffer[BUFSIZ];
995 CI ci = &ct->c_ctinfo;
998 if (!(cp = ci->ci_magic))
999 adios (NULL, "internal error(5)");
1001 tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1002 if (tfile == NULL) {
1003 adios("mhbuildsbr", "unable to create temporary file");
1005 ce->ce_file = add (tfile, NULL);
1010 /* Get buffer ready to go */
1013 buflen = sizeof(buffer);
1016 * Parse composition string into buffer
1018 for ( ; *cp; cp++) {
1023 /* insert parameters from directive */
1027 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1028 snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1038 /* %f, and stdout is not-redirected */
1044 * insert temporary filename where
1045 * content should be written
1047 snprintf (bp, buflen, "%s", ce->ce_file);
1051 /* insert content subtype */
1052 strncpy (bp, ci->ci_subtype, buflen);
1056 /* insert character % */
1077 printf ("composing content %s/%s from command\n\t%s\n",
1078 ci->ci_type, ci->ci_subtype, buffer);
1080 fflush (stdout); /* not sure if need for -noverbose */
1087 if ((out = fopen (ce->ce_file, "w")) == NULL)
1088 adios (ce->ce_file, "unable to open for writing");
1090 for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++)
1094 adios ("fork", "unable to fork");
1099 dup2 (fileno (out), 1);
1100 close (fileno (out));
1101 execvp ("/bin/sh", vec);
1102 fprintf (stderr, "unable to exec ");
1109 if (pidXwait(child_id, NULL))
1115 /* Check size of file */
1116 if (listsw && ct->c_end == 0L) {
1119 if (stat (ce->ce_file, &st) != NOTOK)
1120 ct->c_end = (long) st.st_size;
1132 * 1) choose a transfer encoding.
1133 * 2) check for clashes with multipart boundary string.
1134 * 3) for text content, figure out which character set is being used.
1136 * If there is a clash with one of the contents and the multipart boundary,
1137 * this function will exit with NOTOK. This will cause the scanning process
1138 * to be repeated with a different multipart boundary. It is possible
1139 * (although highly unlikely) that this scan will be repeated multiple times.
1143 scan_content (CT ct)
1146 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1147 int checklinelen = 0, linelen = 0; /* check for long lines */
1148 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1149 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1150 int checkebcdic = 0, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
1151 unsigned char *cp = NULL, buffer[BUFSIZ];
1152 struct text *t = NULL;
1154 CE ce = ct->c_cefile;
1157 * handle multipart by scanning all subparts
1158 * and then checking their encoding.
1160 if (ct->c_type == CT_MULTIPART) {
1161 struct multipart *m = (struct multipart *) ct->c_ctparams;
1164 /* initially mark the domain of enclosing multipart as 7bit */
1165 ct->c_encoding = CE_7BIT;
1167 for (part = m->mp_parts; part; part = part->mp_next) {
1168 CT p = part->mp_part;
1170 if (scan_content (p) == NOTOK) /* choose encoding for subpart */
1173 /* if necessary, enlarge encoding for enclosing multipart */
1174 if (p->c_encoding == CE_BINARY)
1175 ct->c_encoding = CE_BINARY;
1176 if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
1177 ct->c_encoding = CE_8BIT;
1184 * Decide what to check while scanning this content.
1186 switch (ct->c_type) {
1190 if (ct->c_subtype == TEXT_PLAIN) {
1195 checkebcdic = ebcdicsw;
1201 case CT_APPLICATION:
1203 checkebcdic = ebcdicsw;
1215 /* don't check anything for message/external */
1216 if (ct->c_subtype == MESSAGE_EXTERNAL)
1226 * Don't check anything for these types,
1227 * since we are forcing use of base64.
1238 * Scan the unencoded content
1240 if (check8bit || checklinelen || checklinespace || checkboundary) {
1241 if ((in = fopen (ce->ce_file, "r")) == NULL)
1242 adios (ce->ce_file, "unable to open for reading");
1243 len = strlen (prefix);
1245 while (fgets (buffer, sizeof(buffer) - 1, in)) {
1247 * Check for 8bit data.
1250 for (cp = buffer; *cp; cp++) {
1251 if (!isascii (*cp)) {
1253 check8bit = 0; /* no need to keep checking */
1256 * Check if character is ebcdic-safe. We only check
1257 * this if also checking for 8bit data.
1259 if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1261 checkebcdic = 0; /* no need to keep checking */
1267 * Check line length.
1269 if (checklinelen && (strlen (buffer) > CPERLIN + 1)) {
1271 checklinelen = 0; /* no need to keep checking */
1275 * Check if line ends with a space.
1277 if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace (*cp)) {
1279 checklinespace = 0; /* no need to keep checking */
1283 * Check if content contains a line that clashes
1284 * with our standard boundary for multipart messages.
1286 if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
1287 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
1291 if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) {
1293 checkboundary = 0; /* no need to keep checking */
1301 * Decide which transfer encoding to use.
1303 switch (ct->c_type) {
1306 * If the text content didn't specify a character
1307 * set, we need to figure out which one was used.
1309 t = (struct text *) ct->c_ctparams;
1310 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1311 CI ci = &ct->c_ctinfo;
1314 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1318 t->tx_charset = CHARSET_UNKNOWN;
1319 *ap = concat ("charset=", write_charset_8bit(), NULL);
1321 t->tx_charset = CHARSET_USASCII;
1322 *ap = add ("charset=us-ascii", NULL);
1325 cp = strchr(*ap++, '=');
1331 if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1332 ct->c_encoding = CE_QUOTED;
1334 ct->c_encoding = CE_7BIT;
1337 case CT_APPLICATION:
1338 /* For application type, use base64, except when postscript */
1339 if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1340 ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
1341 ? CE_QUOTED : CE_BASE64;
1343 ct->c_encoding = CE_7BIT;
1347 ct->c_encoding = CE_7BIT;
1353 /* For audio, image, and video contents, just use base64 */
1354 ct->c_encoding = CE_BASE64;
1358 return (boundaryclash ? NOTOK : OK);
1363 * Scan the content structures, and build header
1364 * fields that will need to be output into the
1369 build_headers (CT ct)
1371 int cc, mailbody, len;
1373 char *np, *vp, buffer[BUFSIZ];
1374 CI ci = &ct->c_ctinfo;
1377 * If message is type multipart, then add the multipart
1378 * boundary to the list of attribute/value pairs.
1380 if (ct->c_type == CT_MULTIPART) {
1382 static int level = 0; /* store nesting level */
1386 snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++);
1387 cp = strchr(*ap++ = add (buffer, NULL), '=');
1394 * Skip the output of Content-Type, parameters, content
1395 * description and disposition, and Content-ID if the
1396 * content is of type "message" and the rfc934 compatibility
1397 * flag is set (which means we are inside multipart/digest
1398 * and the switch -rfc934mode was given).
1400 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1404 * output the content type and subtype
1406 np = add (TYPE_FIELD, NULL);
1407 vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1409 /* keep track of length of line */
1410 len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
1411 + strlen (ci->ci_subtype) + 3;
1413 mailbody = ct->c_type == CT_MESSAGE
1414 && ct->c_subtype == MESSAGE_EXTERNAL
1415 && ((struct exbody *) ct->c_ctparams)->eb_body;
1418 * Append the attribute/value pairs to
1419 * the end of the Content-Type line.
1421 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1422 if (mailbody && !mh_strcasecmp (*ap, "body"))
1428 snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1429 if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
1430 vp = add ("\n\t", vp);
1436 vp = add (buffer, vp);
1441 * Append any RFC-822 comment to the end of
1442 * the Content-Type line.
1444 if (ci->ci_comment) {
1445 snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1446 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
1447 vp = add ("\n\t", vp);
1453 vp = add (buffer, vp);
1456 vp = add ("\n", vp);
1457 add_header (ct, np, vp);
1460 * output the Content-ID, unless disabled by -nocontentid
1462 if (contentidsw && ct->c_id) {
1463 np = add (ID_FIELD, NULL);
1464 vp = concat (" ", ct->c_id, NULL);
1465 add_header (ct, np, vp);
1469 * output the Content-Description
1472 np = add (DESCR_FIELD, NULL);
1473 vp = concat (" ", ct->c_descr, NULL);
1474 add_header (ct, np, vp);
1478 * output the Content-Disposition
1481 np = add (DISPO_FIELD, NULL);
1482 vp = concat (" ", ct->c_dispo, NULL);
1483 add_header (ct, np, vp);
1488 * If this is the internal content structure for a
1489 * "message/external", then we are done with the
1490 * headers (since it has no body).
1496 * output the Content-MD5
1499 np = add (MD5_FIELD, NULL);
1500 vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
1501 add_header (ct, np, vp);
1505 * output the Content-Transfer-Encoding
1507 switch (ct->c_encoding) {
1509 /* Nothing to output */
1511 np = add (ENCODING_FIELD, NULL);
1512 vp = concat (" ", "7bit", "\n", NULL);
1513 add_header (ct, np, vp);
1518 if (ct->c_type == CT_MESSAGE)
1519 adios (NULL, "internal error, invalid encoding");
1521 np = add (ENCODING_FIELD, NULL);
1522 vp = concat (" ", "8bit", "\n", NULL);
1523 add_header (ct, np, vp);
1527 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1528 adios (NULL, "internal error, invalid encoding");
1530 np = add (ENCODING_FIELD, NULL);
1531 vp = concat (" ", "quoted-printable", "\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 (" ", "base64", "\n", NULL);
1541 add_header (ct, np, vp);
1545 if (ct->c_type == CT_MESSAGE)
1546 adios (NULL, "internal error, invalid encoding");
1548 np = add (ENCODING_FIELD, NULL);
1549 vp = concat (" ", "binary", "\n", NULL);
1550 add_header (ct, np, vp);
1554 adios (NULL, "unknown transfer encoding in content");
1559 * Additional content specific header processing
1561 switch (ct->c_type) {
1564 struct multipart *m;
1567 m = (struct multipart *) ct->c_ctparams;
1568 for (part = m->mp_parts; part; part = part->mp_next) {
1578 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1581 e = (struct exbody *) ct->c_ctparams;
1582 build_headers (e->eb_content);
1595 static char nib2b64[0x40+1] =
1596 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1599 calculate_digest (CT ct, int asciiP)
1602 char buffer[BUFSIZ], *vp, *op;
1604 unsigned char digest[16];
1605 unsigned char outbuf[25];
1608 CE ce = ct->c_cefile;
1611 if ((in = fopen (ce->ce_file, "r")) == NULL)
1612 adios (ce->ce_file, "unable to open for reading");
1614 /* Initialize md5 context */
1615 MD5Init (&mdContext);
1617 /* calculate md5 message digest */
1619 while (fgets (buffer, sizeof(buffer) - 1, in)) {
1622 cp = buffer + strlen (buffer) - 1;
1623 if ((c = *cp) == '\n')
1626 MD5Update (&mdContext, (unsigned char *) buffer,
1627 (unsigned int) strlen (buffer));
1630 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
1633 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
1634 MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
1637 /* md5 finalization. Write digest and zero md5 context */
1638 MD5Final (digest, &mdContext);
1643 /* print debugging info */
1647 fprintf (stderr, "MD5 digest=");
1648 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1650 fprintf (stderr, "%02x", *dp & 0xff);
1651 fprintf (stderr, "\n");
1654 /* encode the digest using base64 */
1655 for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
1656 cc > 0; cc -= 3, op += 4) {
1660 bits = (*dp++ & 0xff) << 16;
1662 bits |= (*dp++ & 0xff) << 8;
1664 bits |= *dp++ & 0xff;
1667 for (bp = op + 4; bp > op; bits >>= 6)
1668 *--bp = nib2b64[bits & 0x3f];
1676 /* null terminate string */
1679 /* now make copy and return string */
1680 vp = concat (" ", outbuf, "\n", NULL);