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 void content_error (char *, CT, char *, ...);
65 int find_cache (CT, int, int *, char *, char *, int);
68 void free_content (CT);
69 void free_ctinfo (CT);
70 void free_encoding (CT, int);
75 CT build_mime (char *);
80 static int init_decoded_content (CT);
81 static char *fgetstr (char *, int, FILE *);
82 static int user_content (FILE *, char *, char *, CT *);
83 static void set_id (CT, int);
84 static int compose_content (CT);
85 static int scan_content (CT);
86 static int build_headers (CT);
87 static char *calculate_digest (CT, int);
91 * Main routine for translating composition file
92 * into valid MIME message. It translates the draft
93 * into a content structure (actually a tree of content
94 * structures). This message then can be manipulated
95 * in various ways, including being output via
100 build_mime (char *infile)
103 char buf[BUFSIZ], name[NAMESZ];
110 umask (~m_gmprot ());
112 /* open the composition draft */
113 if ((in = fopen (infile, "r")) == NULL)
114 adios (infile, "unable to open for reading");
117 * Allocate space for primary (outside) content
119 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
120 adios (NULL, "out of memory");
123 * Allocate structure for handling decoded content
124 * for this part. We don't really need this, but
125 * allocate it to remain consistent.
127 init_decoded_content (ct);
130 * Parse some of the header fields in the composition
131 * draft into the linked list of header fields for
132 * the new MIME message.
134 for (compnum = 1, state = FLD;;) {
135 switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
141 /* abort if draft has Mime-Version header field */
142 if (!mh_strcasecmp (name, VRSN_FIELD))
143 adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
145 /* abort if draft has Content-Transfer-Encoding header field */
146 if (!mh_strcasecmp (name, ENCODING_FIELD))
147 adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
149 /* ignore any Content-Type fields in the header */
150 if (!mh_strcasecmp (name, TYPE_FIELD)) {
151 while (state == FLDPLUS)
152 state = m_getfld (state, name, buf, sizeof(buf), in);
156 /* get copies of the buffers */
157 np = add (name, NULL);
158 vp = add (buf, NULL);
160 /* if necessary, get rest of field */
161 while (state == FLDPLUS) {
162 state = m_getfld (state, name, buf, sizeof(buf), in);
163 vp = add (buf, vp); /* add to previous value */
166 /* Now add the header data to the list */
167 add_header (ct, np, vp);
170 /* if this wasn't the last header field, then continue */
176 adios (NULL, "draft has empty body -- no directives!");
181 fseek (in, (long) (-strlen (buf)), SEEK_CUR);
186 adios (NULL, "message format error in component #%d", compnum);
189 adios (NULL, "getfld() returned %d", state);
195 * Now add the MIME-Version header field
196 * to the list of header fields.
198 np = add (VRSN_FIELD, NULL);
199 vp = concat (" ", VRSN_VALUE, "\n", NULL);
200 add_header (ct, np, vp);
203 * We initally assume we will find multiple contents in the
204 * draft. So create a multipart/mixed content to hold everything.
205 * We can remove this later, if it is not needed.
207 if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
209 ct->c_type = CT_MULTIPART;
210 ct->c_subtype = MULTI_MIXED;
211 ct->c_file = add (infile, NULL);
213 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
214 adios (NULL, "out of memory");
215 ct->c_ctparams = (void *) m;
219 * read and parse the composition file
220 * and the directives it contains.
222 while (fgetstr (buf, sizeof(buf) - 1, in)) {
226 if (user_content (in, infile, buf, &p) == DONE) {
227 admonish (NULL, "ignoring spurious #end");
233 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
234 adios (NULL, "out of memory");
241 * close the composition draft since
242 * it's not needed any longer.
246 /* check if any contents were found */
248 adios (NULL, "no content directives found");
251 * If only one content was found, then remove and
252 * free the outer multipart content.
254 if (!m->mp_parts->mp_next) {
257 p = m->mp_parts->mp_part;
258 m->mp_parts->mp_part = NULL;
260 /* move header fields */
261 p->c_first_hf = ct->c_first_hf;
262 p->c_last_hf = ct->c_last_hf;
263 ct->c_first_hf = NULL;
264 ct->c_last_hf = NULL;
273 * Fill out, or expand directives. Parse and execute
274 * commands specified by profile composition strings.
276 compose_content (ct);
278 if ((cp = strchr(prefix, 'a')) == NULL)
279 adios (NULL, "internal error(4)");
282 * Scan the contents. Choose a transfer encoding, and
283 * check if prefix for multipart boundary clashes with
284 * any of the contents.
286 while (scan_content (ct) == NOTOK) {
291 adios (NULL, "giving up trying to find a unique delimiter string");
297 /* Build the rest of the header field structures */
305 * Set up structures for placing unencoded
306 * content when building parts.
310 init_decoded_content (CT ct)
314 if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
315 adios (NULL, "out of memory");
318 ct->c_ceopenfnx = open7Bit; /* since unencoded */
319 ct->c_ceclosefnx = close_encoding;
320 ct->c_cesizefnx = NULL; /* since unencoded */
327 fgetstr (char *s, int n, FILE *stream)
331 for (ep = (cp = s) + n; cp < ep; ) {
334 if (!fgets (cp, n, stream))
335 return (cp != s ? s : NULL);
336 if (cp == s && *cp != '#')
339 cp += (i = strlen (cp)) - 1;
340 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
351 * Parse the composition draft for text and directives.
352 * Do initial setup of Content structure.
356 user_content (FILE *in, char *file, char *buf, CT *ctp)
365 struct str2init *s2i;
370 if (buf[0] == '\n' || strcmp (buf, "#\n") == 0) {
375 /* allocate basic Content structure */
376 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
377 adios (NULL, "out of memory");
380 /* allocate basic structure for handling decoded content */
381 init_decoded_content (ct);
388 * Handle inline text. Check if line
389 * is one of the following forms:
391 * 1) doesn't begin with '#' (implicit directive)
392 * 2) begins with "##" (implicit directive)
393 * 3) begins with "#<"
395 if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
399 char content[BUFSIZ];
403 cp = m_mktemp2(NULL, invo_name, NULL, &out);
404 if (cp == NULL) adios("mhbuildsbr", "unable to create temporary file");
406 /* use a temp file to collect the plain text lines */
407 ce->ce_file = add (cp, NULL);
410 if (buf[0] == '#' && buf[1] == '<') {
411 strncpy (content, buf + 2, sizeof(content));
418 /* the directive is implicit */
419 strncpy (content, "text/plain", sizeof(content));
421 strncpy (buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
425 if (headers >= 0 && uprf (buffer, DESCR_FIELD)
426 && buffer[i = strlen (DESCR_FIELD)] == ':') {
430 ct->c_descr = add (buffer + i + 1, ct->c_descr);
431 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
432 adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
440 adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
448 if (headers >= 0 && uprf (buffer, DISPO_FIELD)
449 && buffer[i = strlen (DISPO_FIELD)] == ':') {
453 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
454 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
455 adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
463 adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
471 if (headers != 1 || buffer[0] != '\n')
477 if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
479 if (buffer[0] == '#') {
482 if (buffer[1] != '#')
484 for (cp = (bp = buffer) + 1; *cp; cp++)
491 ct->c_end = ftell (out);
494 /* parse content type */
495 if (get_ctinfo (content, ct, inlineD) == NOTOK)
498 for (s2i = str2cts; s2i->si_key; s2i++)
499 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
501 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
505 * check type specified (possibly implicitly)
507 switch (ct->c_type = s2i->si_val) {
509 if (!mh_strcasecmp (ci->ci_subtype, "rfc822")) {
510 ct->c_encoding = CE_7BIT;
515 adios (NULL, "it doesn't make sense to define an in-line %s content",
516 ct->c_type == CT_MESSAGE ? "message" : "multipart");
521 if ((ct->c_ctinitfnx = s2i->si_init))
522 (*ct->c_ctinitfnx) (ct);
527 fseek (in, pos, SEEK_SET);
532 * If we've reached this point, the next line
533 * must be some type of explicit directive.
536 /* check if directive is external-type */
537 extrnal = (buf[1] == '@');
539 /* parse directive */
540 if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
543 /* check directive against the list of MIME types */
544 for (s2i = str2cts; s2i->si_key; s2i++)
545 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
549 * Check if the directive specified a valid type.
550 * This will happen if it was one of the following forms:
557 adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
559 switch (ct->c_type = s2i->si_val) {
561 adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
562 ci->ci_type, ci->ci_subtype);
566 if (!mh_strcasecmp (ci->ci_subtype, "partial"))
567 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
568 ci->ci_type, ci->ci_subtype);
569 if (!mh_strcasecmp (ci->ci_subtype, "external-body"))
570 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
571 ci->ci_type, ci->ci_subtype);
574 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
575 ci->ci_type, ci->ci_subtype);
579 if ((ct->c_ctinitfnx = s2i->si_init))
580 (*ct->c_ctinitfnx) (ct);
585 * #@type/subtype (external types directive)
592 adios (NULL, "need external information for \"#@%s/%s\"",
593 ci->ci_type, ci->ci_subtype);
596 snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
601 * Since we are using the current Content structure to
602 * hold information about the type of the external
603 * reference, we need to create another Content structure
604 * for the message/external-body to wrap it in.
606 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
607 adios (NULL, "out of memory");
610 if (get_ctinfo (buffer, ct, 0) == NOTOK)
612 ct->c_type = CT_MESSAGE;
613 ct->c_subtype = MESSAGE_EXTERNAL;
615 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
616 adios (NULL, "out of memory");
617 ct->c_ctparams = (void *) e;
623 if (params_external (ct, 1) == NOTOK)
629 /* Handle [file] argument */
631 /* check if specifies command to execute */
632 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
633 for (cp = ci->ci_magic + 1; isspace (*cp); cp++)
636 adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
641 /* record filename of decoded contents */
642 ce->ce_file = ci->ci_magic;
643 if (access (ce->ce_file, R_OK) == NOTOK)
644 adios ("reading", "unable to access %s for", ce->ce_file);
645 if (listsw && stat (ce->ce_file, &st) != NOTOK)
646 ct->c_end = (long) st.st_size;
653 * No [file] argument, so check profile for
654 * method to compose content.
656 snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
657 invo_name, ci->ci_type, ci->ci_subtype);
658 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
659 snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type);
660 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
661 content_error (NULL, ct, "don't know how to compose content");
665 ci->ci_magic = add (cp, NULL);
670 adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
674 * #forw [+folder] [msgs]
676 if (!mh_strcasecmp (ci->ci_type, "forw")) {
678 char *folder, *arguments[MAXARGS];
682 ap = brkstring (ci->ci_magic, " ", "\n");
683 copyip (ap, arguments, MAXARGS);
685 arguments[0] = "cur";
690 /* search the arguments for a folder name */
691 for (ap = arguments; *ap; ap++) {
693 if (*cp == '+' || *cp == '@') {
695 adios (NULL, "only one folder per #forw directive");
697 folder = pluspath (cp);
701 /* else, use the current folder */
703 folder = add (getfolder (1), NULL);
705 if (!(mp = folder_read (folder)))
706 adios (NULL, "unable to read folder %s", folder);
707 for (ap = arguments; *ap; ap++) {
709 if (*cp != '+' && *cp != '@')
710 if (!m_convert (mp, cp))
717 * If there is more than one message to include, make this
718 * a content of type "multipart/digest" and insert each message
719 * as a subpart. If there is only one message, then make this
720 * a content of type "message/rfc822".
722 if (mp->numsel > 1) {
723 /* we are forwarding multiple messages */
724 if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
726 ct->c_type = CT_MULTIPART;
727 ct->c_subtype = MULTI_DIGEST;
729 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
730 adios (NULL, "out of memory");
731 ct->c_ctparams = (void *) m;
734 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
735 if (is_selected(mp, msgnum)) {
740 if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
741 adios (NULL, "out of memory");
742 init_decoded_content (p);
744 if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
746 p->c_type = CT_MESSAGE;
747 p->c_subtype = MESSAGE_RFC822;
749 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
750 pe->ce_file = add (buffer, NULL);
751 if (listsw && stat (pe->ce_file, &st) != NOTOK)
752 p->c_end = (long) st.st_size;
754 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
755 adios (NULL, "out of memory");
762 /* we are forwarding one message */
763 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
765 ct->c_type = CT_MESSAGE;
766 ct->c_subtype = MESSAGE_RFC822;
769 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
770 ce->ce_file = add (buffer, NULL);
771 if (listsw && stat (ce->ce_file, &st) != NOTOK)
772 ct->c_end = (long) st.st_size;
775 folder_free (mp); /* free folder/message structure */
782 if (!mh_strcasecmp (ci->ci_type, "end")) {
789 * #begin [ alternative | parallel ]
791 if (!mh_strcasecmp (ci->ci_type, "begin")) {
794 cp = SubMultiPart[vrsn - 1].kv_key;
795 } else if (!mh_strcasecmp (ci->ci_magic, "alternative")) {
796 vrsn = MULTI_ALTERNATE;
797 cp = SubMultiPart[vrsn - 1].kv_key;
798 } else if (!mh_strcasecmp (ci->ci_magic, "parallel")) {
799 vrsn = MULTI_PARALLEL;
800 cp = SubMultiPart[vrsn - 1].kv_key;
801 } else if (uprf (ci->ci_magic, "digest")) {
804 vrsn = MULTI_UNKNOWN;
809 snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
810 if (get_ctinfo (buffer, ct, 0) == NOTOK)
812 ct->c_type = CT_MULTIPART;
813 ct->c_subtype = vrsn;
815 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
816 adios (NULL, "out of memory");
817 ct->c_ctparams = (void *) m;
820 while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
824 if (user_content (in, file, buffer, &p) == DONE) {
826 adios (NULL, "empty \"#begin ... #end\" sequence");
832 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
833 adios (NULL, "out of memory");
838 admonish (NULL, "premature end-of-file, missing #end");
845 adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
846 return NOTOK; /* NOT REACHED */
851 set_id (CT ct, int top)
855 static time_t clock = 0;
860 snprintf (msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
861 (int) getpid(), (long) clock, LocalName(1));
863 msgfmt = getcpy(msgid);
865 snprintf (msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
866 ct->c_id = getcpy (msgid);
870 static char ebcdicsafe[0x100] = {
871 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
872 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
873 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
874 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
875 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
876 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
877 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
878 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
879 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
880 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
881 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
882 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
883 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
884 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
885 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
886 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
887 0x00, 0x00, 0x00, 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
907 * Fill out, or expand the various contents in the composition
908 * draft. Read-in any necessary files. Parse and execute any
909 * commands specified by profile composition strings.
913 compose_content (CT ct)
915 CE ce = ct->c_cefile;
917 switch (ct->c_type) {
922 char partnam[BUFSIZ];
923 struct multipart *m = (struct multipart *) ct->c_ctparams;
927 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
928 pp = partnam + strlen (partnam);
933 /* first, we call compose_content on all the subparts */
934 for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
935 CT p = part->mp_part;
937 sprintf (pp, "%d", partnum);
938 p->c_partno = add (partnam, NULL);
939 if (compose_content (p) == NOTOK)
944 * If the -rfc934mode switch is given, then check all
945 * the subparts of a multipart/digest. If they are all
946 * message/rfc822, then mark this content and all
947 * subparts with the rfc934 compatibility mode flag.
949 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
952 for (part = m->mp_parts; part; part = part->mp_next) {
953 CT p = part->mp_part;
955 if (p->c_subtype != MESSAGE_RFC822) {
960 ct->c_rfc934 = is934;
961 for (part = m->mp_parts; part; part = part->mp_next) {
962 CT p = part->mp_part;
964 if ((p->c_rfc934 = is934))
970 ct->c_end = (partnum = strlen (prefix) + 2) + 2;
974 for (part = m->mp_parts; part; part = part->mp_next)
975 ct->c_end += part->mp_part->c_end + partnum;
981 /* Nothing to do for type message */
985 * Discrete types (text/application/audio/image/video)
990 int i, xstdout, len, buflen;
992 char *vec[4], buffer[BUFSIZ];
994 CI ci = &ct->c_ctinfo;
997 if (!(cp = ci->ci_magic))
998 adios (NULL, "internal error(5)");
1000 tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
1001 if (tfile == NULL) {
1002 adios("mhbuildsbr", "unable to create temporary file");
1004 ce->ce_file = add (tfile, NULL);
1009 /* Get buffer ready to go */
1012 buflen = sizeof(buffer);
1015 * Parse composition string into buffer
1017 for ( ; *cp; cp++) {
1022 /* insert parameters from directive */
1026 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1027 snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
1037 /* %f, and stdout is not-redirected */
1043 * insert temporary filename where
1044 * content should be written
1046 snprintf (bp, buflen, "%s", ce->ce_file);
1050 /* insert content subtype */
1051 strncpy (bp, ci->ci_subtype, buflen);
1055 /* insert character % */
1076 printf ("composing content %s/%s from command\n\t%s\n",
1077 ci->ci_type, ci->ci_subtype, buffer);
1079 fflush (stdout); /* not sure if need for -noverbose */
1086 if ((out = fopen (ce->ce_file, "w")) == NULL)
1087 adios (ce->ce_file, "unable to open for writing");
1089 for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++)
1093 adios ("fork", "unable to fork");
1098 dup2 (fileno (out), 1);
1099 close (fileno (out));
1100 execvp ("/bin/sh", vec);
1101 fprintf (stderr, "unable to exec ");
1108 if (pidXwait(child_id, NULL))
1114 /* Check size of file */
1115 if (listsw && ct->c_end == 0L) {
1118 if (stat (ce->ce_file, &st) != NOTOK)
1119 ct->c_end = (long) st.st_size;
1131 * 1) choose a transfer encoding.
1132 * 2) check for clashes with multipart boundary string.
1133 * 3) for text content, figure out which character set is being used.
1135 * If there is a clash with one of the contents and the multipart boundary,
1136 * this function will exit with NOTOK. This will cause the scanning process
1137 * to be repeated with a different multipart boundary. It is possible
1138 * (although highly unlikely) that this scan will be repeated multiple times.
1142 scan_content (CT ct)
1145 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1146 int checklinelen = 0, linelen = 0; /* check for long lines */
1147 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1148 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1149 int checkebcdic = 0, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
1150 unsigned char *cp = NULL, buffer[BUFSIZ];
1151 struct text *t = NULL;
1153 CE ce = ct->c_cefile;
1156 * handle multipart by scanning all subparts
1157 * and then checking their encoding.
1159 if (ct->c_type == CT_MULTIPART) {
1160 struct multipart *m = (struct multipart *) ct->c_ctparams;
1163 /* initially mark the domain of enclosing multipart as 7bit */
1164 ct->c_encoding = CE_7BIT;
1166 for (part = m->mp_parts; part; part = part->mp_next) {
1167 CT p = part->mp_part;
1169 if (scan_content (p) == NOTOK) /* choose encoding for subpart */
1172 /* if necessary, enlarge encoding for enclosing multipart */
1173 if (p->c_encoding == CE_BINARY)
1174 ct->c_encoding = CE_BINARY;
1175 if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
1176 ct->c_encoding = CE_8BIT;
1183 * Decide what to check while scanning this content.
1185 switch (ct->c_type) {
1189 if (ct->c_subtype == TEXT_PLAIN) {
1194 checkebcdic = ebcdicsw;
1200 case CT_APPLICATION:
1202 checkebcdic = ebcdicsw;
1214 /* don't check anything for message/external */
1215 if (ct->c_subtype == MESSAGE_EXTERNAL)
1225 * Don't check anything for these types,
1226 * since we are forcing use of base64.
1237 * Scan the unencoded content
1239 if (check8bit || checklinelen || checklinespace || checkboundary) {
1240 if ((in = fopen (ce->ce_file, "r")) == NULL)
1241 adios (ce->ce_file, "unable to open for reading");
1242 len = strlen (prefix);
1244 while (fgets (buffer, sizeof(buffer) - 1, in)) {
1246 * Check for 8bit data.
1249 for (cp = buffer; *cp; cp++) {
1250 if (!isascii (*cp)) {
1252 check8bit = 0; /* no need to keep checking */
1255 * Check if character is ebcdic-safe. We only check
1256 * this if also checking for 8bit data.
1258 if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
1260 checkebcdic = 0; /* no need to keep checking */
1266 * Check line length.
1268 if (checklinelen && (strlen (buffer) > CPERLIN + 1)) {
1270 checklinelen = 0; /* no need to keep checking */
1274 * Check if line ends with a space.
1276 if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace (*cp)) {
1278 checklinespace = 0; /* no need to keep checking */
1282 * Check if content contains a line that clashes
1283 * with our standard boundary for multipart messages.
1285 if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
1286 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
1290 if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) {
1292 checkboundary = 0; /* no need to keep checking */
1300 * Decide which transfer encoding to use.
1302 switch (ct->c_type) {
1305 * If the text content didn't specify a character
1306 * set, we need to figure out which one was used.
1308 t = (struct text *) ct->c_ctparams;
1309 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1310 CI ci = &ct->c_ctinfo;
1313 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1317 t->tx_charset = CHARSET_UNKNOWN;
1318 *ap = concat ("charset=", write_charset_8bit(), NULL);
1320 t->tx_charset = CHARSET_USASCII;
1321 *ap = add ("charset=us-ascii", NULL);
1324 cp = strchr(*ap++, '=');
1330 if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1331 ct->c_encoding = CE_QUOTED;
1333 ct->c_encoding = CE_7BIT;
1336 case CT_APPLICATION:
1337 /* For application type, use base64, except when postscript */
1338 if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
1339 ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
1340 ? CE_QUOTED : CE_BASE64;
1342 ct->c_encoding = CE_7BIT;
1346 ct->c_encoding = CE_7BIT;
1352 /* For audio, image, and video contents, just use base64 */
1353 ct->c_encoding = CE_BASE64;
1357 return (boundaryclash ? NOTOK : OK);
1362 * Scan the content structures, and build header
1363 * fields that will need to be output into the
1368 build_headers (CT ct)
1370 int cc, mailbody, len;
1372 char *np, *vp, buffer[BUFSIZ];
1373 CI ci = &ct->c_ctinfo;
1376 * If message is type multipart, then add the multipart
1377 * boundary to the list of attribute/value pairs.
1379 if (ct->c_type == CT_MULTIPART) {
1381 static int level = 0; /* store nesting level */
1385 snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++);
1386 cp = strchr(*ap++ = add (buffer, NULL), '=');
1393 * Skip the output of Content-Type, parameters, content
1394 * description and disposition, and Content-ID if the
1395 * content is of type "message" and the rfc934 compatibility
1396 * flag is set (which means we are inside multipart/digest
1397 * and the switch -rfc934mode was given).
1399 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1403 * output the content type and subtype
1405 np = add (TYPE_FIELD, NULL);
1406 vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1408 /* keep track of length of line */
1409 len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
1410 + strlen (ci->ci_subtype) + 3;
1412 mailbody = ct->c_type == CT_MESSAGE
1413 && ct->c_subtype == MESSAGE_EXTERNAL
1414 && ((struct exbody *) ct->c_ctparams)->eb_body;
1417 * Append the attribute/value pairs to
1418 * the end of the Content-Type line.
1420 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1421 if (mailbody && !mh_strcasecmp (*ap, "body"))
1427 snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
1428 if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
1429 vp = add ("\n\t", vp);
1435 vp = add (buffer, vp);
1440 * Append any RFC-822 comment to the end of
1441 * the Content-Type line.
1443 if (ci->ci_comment) {
1444 snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1445 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
1446 vp = add ("\n\t", vp);
1452 vp = add (buffer, vp);
1455 vp = add ("\n", vp);
1456 add_header (ct, np, vp);
1459 * output the Content-ID, unless disabled by -nocontentid
1461 if (contentidsw && ct->c_id) {
1462 np = add (ID_FIELD, NULL);
1463 vp = concat (" ", ct->c_id, NULL);
1464 add_header (ct, np, vp);
1468 * output the Content-Description
1471 np = add (DESCR_FIELD, NULL);
1472 vp = concat (" ", ct->c_descr, NULL);
1473 add_header (ct, np, vp);
1477 * output the Content-Disposition
1480 np = add (DISPO_FIELD, NULL);
1481 vp = concat (" ", ct->c_dispo, NULL);
1482 add_header (ct, np, vp);
1487 * If this is the internal content structure for a
1488 * "message/external", then we are done with the
1489 * headers (since it has no body).
1495 * output the Content-MD5
1498 np = add (MD5_FIELD, NULL);
1499 vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
1500 add_header (ct, np, vp);
1504 * output the Content-Transfer-Encoding
1506 switch (ct->c_encoding) {
1508 /* Nothing to output */
1510 np = add (ENCODING_FIELD, NULL);
1511 vp = concat (" ", "7bit", "\n", NULL);
1512 add_header (ct, np, vp);
1517 if (ct->c_type == CT_MESSAGE)
1518 adios (NULL, "internal error, invalid encoding");
1520 np = add (ENCODING_FIELD, NULL);
1521 vp = concat (" ", "8bit", "\n", NULL);
1522 add_header (ct, np, vp);
1526 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1527 adios (NULL, "internal error, invalid encoding");
1529 np = add (ENCODING_FIELD, NULL);
1530 vp = concat (" ", "quoted-printable", "\n", NULL);
1531 add_header (ct, np, vp);
1535 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1536 adios (NULL, "internal error, invalid encoding");
1538 np = add (ENCODING_FIELD, NULL);
1539 vp = concat (" ", "base64", "\n", NULL);
1540 add_header (ct, np, vp);
1544 if (ct->c_type == CT_MESSAGE)
1545 adios (NULL, "internal error, invalid encoding");
1547 np = add (ENCODING_FIELD, NULL);
1548 vp = concat (" ", "binary", "\n", NULL);
1549 add_header (ct, np, vp);
1553 adios (NULL, "unknown transfer encoding in content");
1558 * Additional content specific header processing
1560 switch (ct->c_type) {
1563 struct multipart *m;
1566 m = (struct multipart *) ct->c_ctparams;
1567 for (part = m->mp_parts; part; part = part->mp_next) {
1577 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1580 e = (struct exbody *) ct->c_ctparams;
1581 build_headers (e->eb_content);
1594 static char nib2b64[0x40+1] =
1595 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1598 calculate_digest (CT ct, int asciiP)
1601 char buffer[BUFSIZ], *vp, *op;
1603 unsigned char digest[16];
1604 unsigned char outbuf[25];
1606 CE ce = ct->c_cefile;
1607 char *infilename = ce->ce_file ? ce->ce_file : ct->c_file;
1611 if ((in = fopen (infilename, "r")) == NULL)
1612 adios (infilename, "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);