3 * mhbuildsbr.c -- routines to expand/translate MIME composition files
7 * This code is Copyright (c) 2002, by the authors of nmh. See the
8 * COPYRIGHT file in the root directory of the nmh distribution for
9 * complete copyright information.
13 * This code was originally part of mhn.c. I split it into
14 * a separate program (mhbuild.c) and then later split it
15 * again (mhbuildsbr.c). But the code still has some of
16 * the mhn.c code in it. This program needs additional
17 * streamlining and removal of unneeded code.
22 #include <h/signals.h>
29 #include <h/mhparse.h>
32 #ifdef TIME_WITH_SYS_TIME
33 # include <sys/time.h>
36 # ifdef TM_IN_SYS_TIME
37 # include <sys/time.h>
43 #ifdef HAVE_SYS_WAIT_H
44 # include <sys/wait.h>
54 extern int contentidsw;
56 extern int endian; /* mhmisc.c */
59 extern int rcachesw; /* mhcachesbr.c */
60 extern int wcachesw; /* mhcachesbr.c */
62 int checksw = 0; /* Add Content-MD5 field */
65 * Directory to place tmp files. This must
66 * be set before these routines are called.
72 static char prefix[] = "----- =_aaaaaaaaaa";
75 * Structure for mapping types to their internal flags
83 * Structures for TEXT messages
85 static struct k2v SubText[] = {
86 { "plain", TEXT_PLAIN },
87 { "richtext", TEXT_RICHTEXT }, /* defined in RFC-1341 */
88 { "enriched", TEXT_ENRICHED }, /* defined in RFC-1896 */
89 { NULL, TEXT_UNKNOWN } /* this one must be last! */
92 static struct k2v Charset[] = {
93 { "us-ascii", CHARSET_USASCII },
94 { "iso-8859-1", CHARSET_LATIN },
95 { NULL, CHARSET_UNKNOWN } /* this one must be last! */
99 * Structures for MULTIPART messages
101 static struct k2v SubMultiPart[] = {
102 { "mixed", MULTI_MIXED },
103 { "alternative", MULTI_ALTERNATE },
104 { "digest", MULTI_DIGEST },
105 { "parallel", MULTI_PARALLEL },
106 { NULL, MULTI_UNKNOWN } /* this one must be last! */
110 * Structures for MESSAGE messages
112 static struct k2v SubMessage[] = {
113 { "rfc822", MESSAGE_RFC822 },
114 { "partial", MESSAGE_PARTIAL },
115 { "external-body", MESSAGE_EXTERNAL },
116 { NULL, MESSAGE_UNKNOWN } /* this one must be last! */
120 * Structure for APPLICATION messages
122 static struct k2v SubApplication[] = {
123 { "octet-stream", APPLICATION_OCTETS },
124 { "postscript", APPLICATION_POSTSCRIPT },
125 { NULL, APPLICATION_UNKNOWN } /* this one must be last! */
130 int make_intermediates (char *);
131 void content_error (char *, CT, char *, ...);
134 int find_cache (CT, int, int *, char *, char *, int);
137 int ftp_get (char *, char *, char *, char *, char *, char *, int, int);
140 void free_content (CT);
141 void free_ctinfo (CT);
142 void free_encoding (CT, int);
147 CT build_mime (char *);
153 static CT get_content (FILE *, char *, int);
154 static int add_header (CT, char *, char *);
155 static int get_ctinfo (char *, CT, int);
156 static int get_comment (CT, char **, int);
157 static int InitGeneric (CT);
158 static int InitText (CT);
159 static int InitMultiPart (CT);
160 static void reverse_parts (CT);
161 static int InitMessage (CT);
162 static int params_external (CT, int);
163 static int InitApplication (CT);
164 static int init_decoded_content (CT);
165 static int init_encoding (CT, OpenCEFunc);
166 static void close_encoding (CT);
167 static unsigned long size_encoding (CT);
168 static int InitBase64 (CT);
169 static int openBase64 (CT, char **);
170 static int InitQuoted (CT);
171 static int openQuoted (CT, char **);
172 static int Init7Bit (CT);
173 static int open7Bit (CT, char **);
174 static int openExternal (CT, CT, CE, char **, int *);
175 static int InitFile (CT);
176 static int openFile (CT, char **);
177 static int InitFTP (CT);
178 static int openFTP (CT, char **);
179 static int InitMail (CT);
180 static int openMail (CT, char **);
181 static char *fgetstr (char *, int, FILE *);
182 static int user_content (FILE *, char *, char *, CT *);
183 static void set_id (CT, int);
184 static int compose_content (CT);
185 static int scan_content (CT);
186 static int build_headers (CT);
187 static char *calculate_digest (CT, int);
188 static int readDigest (CT, char *);
189 static char *incl_name_value (char *, char *, char *);
190 static char *extract_name_value (char *, char *);
193 * Structures for mapping (content) types to
194 * the functions to handle them.
202 static struct str2init str2cts[] = {
203 { "application", CT_APPLICATION, InitApplication },
204 { "audio", CT_AUDIO, InitGeneric },
205 { "image", CT_IMAGE, InitGeneric },
206 { "message", CT_MESSAGE, InitMessage },
207 { "multipart", CT_MULTIPART, InitMultiPart },
208 { "text", CT_TEXT, InitText },
209 { "video", CT_VIDEO, InitGeneric },
210 { NULL, CT_EXTENSION, NULL }, /* these two must be last! */
211 { NULL, CT_UNKNOWN, NULL },
214 static struct str2init str2ces[] = {
215 { "base64", CE_BASE64, InitBase64 },
216 { "quoted-printable", CE_QUOTED, InitQuoted },
217 { "8bit", CE_8BIT, Init7Bit },
218 { "7bit", CE_7BIT, Init7Bit },
219 { "binary", CE_BINARY, NULL },
220 { NULL, CE_EXTENSION, NULL }, /* these two must be last! */
221 { NULL, CE_UNKNOWN, NULL },
225 * NOTE WELL: si_key MUST NOT have value of NOTOK
227 * si_key is 1 if access method is anonymous.
229 static struct str2init str2methods[] = {
230 { "afs", 1, InitFile },
231 { "anon-ftp", 1, InitFTP },
232 { "ftp", 0, InitFTP },
233 { "local-file", 0, InitFile },
234 { "mail-server", 0, InitMail },
240 pidcheck (int status)
242 if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT)
252 * Main routine for translating composition file
253 * into valid MIME message. It translates the draft
254 * into a content structure (actually a tree of content
255 * structures). This message then can be manipulated
256 * in various ways, including being output via
261 build_mime (char *infile)
264 char buf[BUFSIZ], name[NAMESZ];
271 umask (~m_gmprot ());
273 /* open the composition draft */
274 if ((in = fopen (infile, "r")) == NULL)
275 adios (infile, "unable to open for reading");
278 * Allocate space for primary (outside) content
280 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
281 adios (NULL, "out of memory");
284 * Allocate structure for handling decoded content
285 * for this part. We don't really need this, but
286 * allocate it to remain consistent.
288 init_decoded_content (ct);
291 * Parse some of the header fields in the composition
292 * draft into the linked list of header fields for
293 * the new MIME message.
295 for (compnum = 1, state = FLD;;) {
296 switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
302 /* abort if draft has Mime-Version header field */
303 if (!mh_strcasecmp (name, VRSN_FIELD))
304 adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD);
306 /* abort if draft has Content-Transfer-Encoding header field */
307 if (!mh_strcasecmp (name, ENCODING_FIELD))
308 adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD);
310 /* ignore any Content-Type fields in the header */
311 if (!mh_strcasecmp (name, TYPE_FIELD)) {
312 while (state == FLDPLUS)
313 state = m_getfld (state, name, buf, sizeof(buf), in);
317 /* get copies of the buffers */
318 np = add (name, NULL);
319 vp = add (buf, NULL);
321 /* if necessary, get rest of field */
322 while (state == FLDPLUS) {
323 state = m_getfld (state, name, buf, sizeof(buf), in);
324 vp = add (buf, vp); /* add to previous value */
327 /* Now add the header data to the list */
328 add_header (ct, np, vp);
331 /* if this wasn't the last header field, then continue */
337 adios (NULL, "draft has empty body -- no directives!");
342 fseek (in, (long) (-strlen (buf)), SEEK_CUR);
347 adios (NULL, "message format error in component #%d", compnum);
350 adios (NULL, "getfld() returned %d", state);
356 * Now add the MIME-Version header field
357 * to the list of header fields.
359 np = add (VRSN_FIELD, NULL);
360 vp = concat (" ", VRSN_VALUE, "\n", NULL);
361 add_header (ct, np, vp);
364 * We initally assume we will find multiple contents in the
365 * draft. So create a multipart/mixed content to hold everything.
366 * We can remove this later, if it is not needed.
368 if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
370 ct->c_type = CT_MULTIPART;
371 ct->c_subtype = MULTI_MIXED;
372 ct->c_file = add (infile, NULL);
374 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
375 adios (NULL, "out of memory");
376 ct->c_ctparams = (void *) m;
380 * read and parse the composition file
381 * and the directives it contains.
383 while (fgetstr (buf, sizeof(buf) - 1, in)) {
387 if (user_content (in, infile, buf, &p) == DONE) {
388 admonish (NULL, "ignoring spurious #end");
394 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
395 adios (NULL, "out of memory");
402 * close the composition draft since
403 * it's not needed any longer.
407 /* check if any contents were found */
409 adios (NULL, "no content directives found");
412 * If only one content was found, then remove and
413 * free the outer multipart content.
415 if (!m->mp_parts->mp_next) {
418 p = m->mp_parts->mp_part;
419 m->mp_parts->mp_part = NULL;
421 /* move header fields */
422 p->c_first_hf = ct->c_first_hf;
423 p->c_last_hf = ct->c_last_hf;
424 ct->c_first_hf = NULL;
425 ct->c_last_hf = NULL;
434 * Fill out, or expand directives. Parse and execute
435 * commands specified by profile composition strings.
437 compose_content (ct);
439 if ((cp = strchr(prefix, 'a')) == NULL)
440 adios (NULL, "internal error(4)");
443 * Scan the contents. Choose a transfer encoding, and
444 * check if prefix for multipart boundary clashes with
445 * any of the contents.
447 while (scan_content (ct) == NOTOK) {
452 adios (NULL, "giving up trying to find a unique delimiter string");
458 /* Build the rest of the header field structures */
466 * Main routine for reading/parsing the headers
467 * of a message content.
469 * toplevel = 1 # we are at the top level of the message
470 * toplevel = 0 # we are inside message type or multipart type
471 * # other than multipart/digest
472 * toplevel = -1 # we are inside multipart/digest
476 get_content (FILE *in, char *file, int toplevel)
479 char buf[BUFSIZ], name[NAMESZ];
482 if (!(ct = (CT) calloc (1, sizeof(*ct))))
483 adios (NULL, "out of memory");
486 ct->c_file = add (file, NULL);
487 ct->c_begin = ftell (ct->c_fp) + 1;
490 * Read the content headers
492 for (compnum = 1, state = FLD;;) {
493 switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
499 /* Get MIME-Version field */
500 if (!mh_strcasecmp (name, VRSN_FIELD)) {
504 cp = add (buf, NULL);
505 while (state == FLDPLUS) {
506 state = m_getfld (state, name, buf, sizeof(buf), in);
511 advise (NULL, "message %s has multiple %s: fields (%s)",
512 ct->c_file, VRSN_FIELD, dp = trimcpy (cp));
519 while (isspace (*cp))
521 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
523 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
528 fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp);
530 if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK)
533 for (dp = cp; istoken (*dp); dp++)
536 ucmp = !mh_strcasecmp (cp, VRSN_VALUE);
540 "message %s has unknown value for %s: field (%s)",
541 ct->c_file, VRSN_FIELD, cp);
545 /* Get Content-Type field */
546 if (!mh_strcasecmp (name, TYPE_FIELD)) {
548 struct str2init *s2i;
549 CI ci = &ct->c_ctinfo;
551 cp = add (buf, NULL);
552 while (state == FLDPLUS) {
553 state = m_getfld (state, name, buf, sizeof(buf), in);
557 /* Check if we've already seen a Content-Type header */
559 char *dp = trimcpy (cp);
561 advise (NULL, "message %s has multiple %s: fields (%s)",
562 ct->c_file, TYPE_FIELD, dp);
568 /* Parse the Content-Type field */
569 if (get_ctinfo (cp, ct, 0) == NOTOK)
573 * Set the Init function and the internal
574 * flag for this content type.
576 for (s2i = str2cts; s2i->si_key; s2i++)
577 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
579 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
581 ct->c_type = s2i->si_val;
582 ct->c_ctinitfnx = s2i->si_init;
586 /* Get Content-Transfer-Encoding field */
587 if (!mh_strcasecmp (name, ENCODING_FIELD)) {
590 struct str2init *s2i;
592 cp = add (buf, NULL);
593 while (state == FLDPLUS) {
594 state = m_getfld (state, name, buf, sizeof(buf), in);
599 * Check if we've already seen the
600 * Content-Transfer-Encoding field
603 advise (NULL, "message %s has multiple %s: fields (%s)",
604 ct->c_file, ENCODING_FIELD, dp = trimcpy (cp));
610 ct->c_celine = cp; /* Save copy of this field */
611 while (isspace (*cp))
613 for (dp = cp; istoken (*dp); dp++)
619 * Find the internal flag and Init function
620 * for this transfer encoding.
622 for (s2i = str2ces; s2i->si_key; s2i++)
623 if (!mh_strcasecmp (cp, s2i->si_key))
625 if (!s2i->si_key && !uprf (cp, "X-"))
628 ct->c_encoding = s2i->si_val;
630 /* Call the Init function for this encoding */
631 if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK)
636 /* Get Content-ID field */
637 if (!mh_strcasecmp (name, ID_FIELD)) {
638 ct->c_id = add (buf, ct->c_id);
639 while (state == FLDPLUS) {
640 state = m_getfld (state, name, buf, sizeof(buf), in);
641 ct->c_id = add (buf, ct->c_id);
646 /* Get Content-Description field */
647 if (!mh_strcasecmp (name, DESCR_FIELD)) {
648 ct->c_descr = add (buf, ct->c_descr);
649 while (state == FLDPLUS) {
650 state = m_getfld (state, name, buf, sizeof(buf), in);
651 ct->c_descr = add (buf, ct->c_descr);
656 /* Get Content-Disposition field */
657 if (!mh_strcasecmp (name, DISPO_FIELD)) {
658 ct->c_dispo = add (buf, ct->c_dispo);
659 while (state == FLDPLUS) {
660 state = m_getfld (state, name, buf, sizeof(buf), in);
661 ct->c_dispo = add (buf, ct->c_dispo);
666 /* Get Content-MD5 field */
667 if (!mh_strcasecmp (name, MD5_FIELD)) {
670 cp = add (buf, NULL);
671 while (state == FLDPLUS) {
672 state = m_getfld (state, name, buf, sizeof(buf), in);
681 if (ct->c_digested) {
682 advise (NULL, "message %s has multiple %s: fields (%s)",
683 ct->c_file, MD5_FIELD, dp = trimcpy (cp));
690 while (isspace (*cp))
692 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
694 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
699 fprintf (stderr, "%s: %s\n", MD5_FIELD, cp);
701 if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) {
706 for (dp = cp; *dp && !isspace (*dp); dp++)
717 if (uprf (name, XXX_FIELD_PRF))
718 advise (NULL, "unknown field (%s) in message %s",
723 while (state == FLDPLUS)
724 state = m_getfld (state, name, buf, sizeof(buf), in);
727 if (state != FLDEOF) {
728 ct->c_begin = ftell (in) + 1;
735 ct->c_begin = ftell (in) - strlen (buf);
739 ct->c_begin = ftell (in);
744 adios (NULL, "message format error in component #%d", compnum);
747 adios (NULL, "getfld() returned %d", state);
753 * Check if we saw a Content-Type field.
754 * If not, then assign a default value for
755 * it, and the Init function.
759 * If we are inside a multipart/digest message,
760 * so default type is message/rfc822
763 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
765 ct->c_type = CT_MESSAGE;
766 ct->c_ctinitfnx = InitMessage;
769 * Else default type is text/plain
771 if (get_ctinfo ("text/plain", ct, 0) == NOTOK)
773 ct->c_type = CT_TEXT;
774 ct->c_ctinitfnx = InitText;
778 /* Use default Transfer-Encoding, if necessary */
780 ct->c_encoding = CE_7BIT;
793 * small routine to add header field to list
797 add_header (CT ct, char *name, char *value)
801 /* allocate header field structure */
802 hp = mh_xmalloc (sizeof(*hp));
804 /* link data into header structure */
809 /* link header structure into the list */
810 if (ct->c_first_hf == NULL) {
811 ct->c_first_hf = hp; /* this is the first */
814 ct->c_last_hf->next = hp; /* add it to the end */
823 * Used to parse both:
824 * 1) Content-Type line
825 * 2) composition directives
827 * and fills in the information of the CTinfo structure.
831 get_ctinfo (char *cp, CT ct, int magic)
834 char *dp, **ap, **ep;
839 i = strlen (invo_name) + 2;
841 /* store copy of Content-Type line */
842 cp = ct->c_ctline = add (cp, NULL);
844 while (isspace (*cp)) /* trim leading spaces */
847 /* change newlines to spaces */
848 for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
851 /* trim trailing spaces */
852 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
858 fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp);
860 if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
863 for (dp = cp; istoken (*dp); dp++)
866 ci->ci_type = add (cp, NULL); /* store content type */
870 advise (NULL, "invalid %s: field in message %s (empty type)",
871 TYPE_FIELD, ct->c_file);
875 /* down case the content type string */
876 for (dp = ci->ci_type; *dp; dp++)
877 if (isalpha(*dp) && isupper (*dp))
880 while (isspace (*cp))
883 if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
888 ci->ci_subtype = add ("", NULL);
893 while (isspace (*cp))
896 if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
899 for (dp = cp; istoken (*dp); dp++)
902 ci->ci_subtype = add (cp, NULL); /* store the content subtype */
905 if (!*ci->ci_subtype) {
907 "invalid %s: field in message %s (empty subtype for \"%s\")",
908 TYPE_FIELD, ct->c_file, ci->ci_type);
912 /* down case the content subtype string */
913 for (dp = ci->ci_subtype; *dp; dp++)
914 if (isalpha(*dp) && isupper (*dp))
918 while (isspace (*cp))
921 if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
925 * Parse attribute/value pairs given with Content-Type
927 ep = (ap = ci->ci_attrs) + NPARMS;
933 "too many parameters in message %s's %s: field (%d max)",
934 ct->c_file, TYPE_FIELD, NPARMS);
939 while (isspace (*cp))
942 if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
947 "extraneous trailing ';' in message %s's %s: parameter list",
948 ct->c_file, TYPE_FIELD);
952 /* down case the attribute name */
953 for (dp = cp; istoken (*dp); dp++)
954 if (isalpha(*dp) && isupper (*dp))
957 for (up = dp; isspace (*dp); )
959 if (dp == cp || *dp != '=') {
961 "invalid parameter in message %s's %s: field\n%*.*sparameter %s (error detected at offset %d)",
962 ct->c_file, TYPE_FIELD, i, i, "", cp, dp - cp);
966 vp = (*ap = add (cp, NULL)) + (up - cp);
968 for (dp++; isspace (*dp); )
971 /* now add the attribute value */
972 ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp);
975 for (cp = ++dp, dp = vp;;) {
980 "invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)",
981 ct->c_file, TYPE_FIELD, i, i, "", *ap);
986 if ((c = *cp++) == '\0')
1001 for (cp = dp, dp = vp; istoken (*cp); cp++, dp++)
1007 "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)",
1008 ct->c_file, TYPE_FIELD, i, i, "", *ap);
1013 while (isspace (*cp))
1016 if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
1021 * Get any <Content-Id> given in buffer
1023 if (magic && *cp == '<') {
1028 if (!(dp = strchr(ct->c_id = ++cp, '>'))) {
1029 advise (NULL, "invalid ID in message %s", ct->c_file);
1035 ct->c_id = concat ("<", ct->c_id, ">\n", NULL);
1041 while (isspace (*cp))
1046 * Get any [Content-Description] given in buffer.
1048 if (magic && *cp == '[') {
1050 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
1054 advise (NULL, "invalid description in message %s", ct->c_file);
1062 ct->c_descr = concat (ct->c_descr, "\n", NULL);
1068 while (isspace (*cp))
1073 * Get any {Content-Disposition} given in buffer.
1075 if (magic && *cp == '{') {
1077 for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
1081 advise (NULL, "invalid disposition in message %s", ct->c_file);
1089 ct->c_dispo = concat (ct->c_dispo, "\n", NULL);
1095 while (isspace (*cp))
1100 * Check if anything is left over
1104 ci->ci_magic = add (cp, NULL);
1106 /* If there is a Content-Disposition header and it doesn't
1107 have a *filename=, extract it from the magic contents.
1108 The r1bindex call skips any leading directory
1112 incl_name_value (ct->c_dispo,
1114 r1bindex (extract_name_value ("name",
1121 "extraneous information in message %s's %s: field\n%*.*s(%s)",
1122 ct->c_file, TYPE_FIELD, i, i, "", cp);
1130 get_comment (CT ct, char **ap, int istype)
1134 char c, buffer[BUFSIZ], *dp;
1143 switch (c = *cp++) {
1146 advise (NULL, "invalid comment in message %s's %s: field",
1147 ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD);
1152 if ((c = *cp++) == '\0')
1175 if ((dp = ci->ci_comment)) {
1176 ci->ci_comment = concat (dp, " ", buffer, NULL);
1179 ci->ci_comment = add (buffer, NULL);
1183 while (isspace (*cp))
1194 * Handles content types audio, image, and video.
1195 * There's not much to do right here.
1201 return OK; /* not much to do here */
1215 CI ci = &ct->c_ctinfo;
1217 /* check for missing subtype */
1218 if (!*ci->ci_subtype)
1219 ci->ci_subtype = add ("plain", ci->ci_subtype);
1222 for (kv = SubText; kv->kv_key; kv++)
1223 if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1225 ct->c_subtype = kv->kv_value;
1227 /* allocate text character set structure */
1228 if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL)
1229 adios (NULL, "out of memory");
1230 ct->c_ctparams = (void *) t;
1232 /* initially mark character set as unspecified */
1233 t->tx_charset = CHARSET_UNSPECIFIED;
1235 /* scan for charset parameter */
1236 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1237 if (!mh_strcasecmp (*ap, "charset"))
1240 /* check if content specified a character set */
1242 /* match character set or set to CHARSET_UNKNOWN */
1243 for (kv = Charset; kv->kv_key; kv++)
1244 if (!mh_strcasecmp (*ep, kv->kv_key))
1246 t->tx_charset = kv->kv_value;
1258 InitMultiPart (CT ct)
1262 char *cp, *dp, **ap, **ep;
1263 char *bp, buffer[BUFSIZ];
1264 struct multipart *m;
1266 struct part *part, **next;
1267 CI ci = &ct->c_ctinfo;
1272 * The encoding for multipart messages must be either
1273 * 7bit, 8bit, or binary (per RFC2045).
1275 if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT
1276 && ct->c_encoding != CE_BINARY) {
1278 "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary",
1279 ci->ci_type, ci->ci_subtype, ct->c_file);
1284 for (kv = SubMultiPart; kv->kv_key; kv++)
1285 if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1287 ct->c_subtype = kv->kv_value;
1290 * Check for "boundary" parameter, which is
1291 * required for multipart messages.
1293 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1294 if (!mh_strcasecmp (*ap, "boundary")) {
1300 /* complain if boundary parameter is missing */
1303 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1304 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1308 /* allocate primary structure for multipart info */
1309 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
1310 adios (NULL, "out of memory");
1311 ct->c_ctparams = (void *) m;
1313 /* check if boundary parameter contains only whitespace characters */
1314 for (cp = bp; isspace (*cp); cp++)
1317 advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1318 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1322 /* remove trailing whitespace from boundary parameter */
1323 for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
1328 /* record boundary separators */
1329 m->mp_start = concat (bp, "\n", NULL);
1330 m->mp_stop = concat (bp, "--\n", NULL);
1332 if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1333 advise (ct->c_file, "unable to open for reading");
1337 fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
1339 next = &m->mp_parts;
1343 while (fgets (buffer, sizeof(buffer) - 1, fp)) {
1347 pos += strlen (buffer);
1348 if (buffer[0] != '-' || buffer[1] != '-')
1351 if (strcmp (buffer + 2, m->mp_start))
1354 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
1355 adios (NULL, "out of memory");
1357 next = &part->mp_next;
1359 if (!(p = get_content (fp, ct->c_file,
1360 rfc934sw && ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
1368 fseek (fp, pos, SEEK_SET);
1371 if (strcmp (buffer + 2, m->mp_start) == 0) {
1375 p->c_end = ftell(fp) - (strlen(buffer) + 1);
1376 if (p->c_end < p->c_begin)
1377 p->c_begin = p->c_end;
1382 if (strcmp (buffer + 2, m->mp_stop) == 0)
1388 advise (NULL, "bogus multipart content in message %s", ct->c_file);
1389 if (!inout && part) {
1391 p->c_end = ct->c_end;
1393 if (p->c_begin >= p->c_end) {
1394 for (next = &m->mp_parts; *next != part;
1395 next = &((*next)->mp_next))
1399 free ((char *) part);
1404 /* reverse the order of the parts for multipart/alternative */
1405 if (ct->c_subtype == MULTI_ALTERNATE)
1409 * label all subparts with part number, and
1410 * then initialize the content of the subpart.
1415 char partnam[BUFSIZ];
1418 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1419 pp = partnam + strlen (partnam);
1424 for (part = m->mp_parts, partnum = 1; part;
1425 part = part->mp_next, partnum++) {
1428 sprintf (pp, "%d", partnum);
1429 p->c_partno = add (partnam, NULL);
1431 /* initialize the content of the subparts */
1432 if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1447 * reverse the order of the parts of a multipart
1451 reverse_parts (CT ct)
1454 struct multipart *m;
1455 struct part **base, **bmp, **next, *part;
1457 m = (struct multipart *) ct->c_ctparams;
1459 /* if only one part, just return */
1460 if (!m->mp_parts || !m->mp_parts->mp_next)
1463 /* count number of parts */
1465 for (part = m->mp_parts; part; part = part->mp_next)
1468 /* allocate array of pointers to the parts */
1469 if (!(base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base))))
1470 adios (NULL, "out of memory");
1473 /* point at all the parts */
1474 for (part = m->mp_parts; part; part = part->mp_next)
1478 /* reverse the order of the parts */
1479 next = &m->mp_parts;
1480 for (bmp--; bmp >= base; bmp--) {
1483 next = &part->mp_next;
1487 /* free array of pointers */
1488 free ((char *) base);
1500 CI ci = &ct->c_ctinfo;
1502 if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1504 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1505 ci->ci_type, ci->ci_subtype, ct->c_file);
1509 /* check for missing subtype */
1510 if (!*ci->ci_subtype)
1511 ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1514 for (kv = SubMessage; kv->kv_key; kv++)
1515 if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1517 ct->c_subtype = kv->kv_value;
1519 switch (ct->c_subtype) {
1520 case MESSAGE_RFC822:
1523 case MESSAGE_PARTIAL:
1528 if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL)
1529 adios (NULL, "out of memory");
1530 ct->c_ctparams = (void *) p;
1532 /* scan for parameters "id", "number", and "total" */
1533 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1534 if (!mh_strcasecmp (*ap, "id")) {
1535 p->pm_partid = add (*ep, NULL);
1538 if (!mh_strcasecmp (*ap, "number")) {
1539 if (sscanf (*ep, "%d", &p->pm_partno) != 1
1540 || p->pm_partno < 1) {
1543 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1544 *ap, ci->ci_type, ci->ci_subtype,
1545 ct->c_file, TYPE_FIELD);
1550 if (!mh_strcasecmp (*ap, "total")) {
1551 if (sscanf (*ep, "%d", &p->pm_maxno) != 1
1560 || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1562 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1563 ci->ci_type, ci->ci_subtype,
1564 ct->c_file, TYPE_FIELD);
1570 case MESSAGE_EXTERNAL:
1577 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
1578 adios (NULL, "out of memory");
1579 ct->c_ctparams = (void *) e;
1582 && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1583 advise (ct->c_file, "unable to open for reading");
1587 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1589 if (!(p = get_content (fp, ct->c_file, 0))) {
1598 if ((exresult = params_external (ct, 0)) != NOTOK
1599 && p->c_ceopenfnx == openMail) {
1603 if ((size = ct->c_end - p->c_begin) <= 0) {
1605 content_error (NULL, ct,
1606 "empty body for access-type=mail-server");
1610 e->eb_body = bp = mh_xmalloc ((unsigned) size);
1611 fseek (p->c_fp, p->c_begin, SEEK_SET);
1613 switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1615 adios ("failed", "fread");
1618 adios (NULL, "unexpected EOF from fread");
1621 bp += cc, size -= cc;
1628 p->c_end = p->c_begin;
1633 if (exresult == NOTOK)
1635 if (e->eb_flags == NOTOK)
1638 switch (p->c_type) {
1643 if (p->c_subtype != MESSAGE_RFC822)
1647 e->eb_partno = ct->c_partno;
1649 (*p->c_ctinitfnx) (p);
1664 params_external (CT ct, int composing)
1667 struct exbody *e = (struct exbody *) ct->c_ctparams;
1668 CI ci = &ct->c_ctinfo;
1670 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1671 if (!mh_strcasecmp (*ap, "access-type")) {
1672 struct str2init *s2i;
1673 CT p = e->eb_content;
1675 for (s2i = str2methods; s2i->si_key; s2i++)
1676 if (!mh_strcasecmp (*ep, s2i->si_key))
1681 e->eb_flags = NOTOK;
1682 p->c_encoding = CE_EXTERNAL;
1685 e->eb_access = s2i->si_key;
1686 e->eb_flags = s2i->si_val;
1687 p->c_encoding = CE_EXTERNAL;
1689 /* Call the Init function for this external type */
1690 if ((*s2i->si_init)(p) == NOTOK)
1694 if (!mh_strcasecmp (*ap, "name")) {
1698 if (!mh_strcasecmp (*ap, "permission")) {
1699 e->eb_permission = *ep;
1702 if (!mh_strcasecmp (*ap, "site")) {
1706 if (!mh_strcasecmp (*ap, "directory")) {
1710 if (!mh_strcasecmp (*ap, "mode")) {
1714 if (!mh_strcasecmp (*ap, "size")) {
1715 sscanf (*ep, "%lu", &e->eb_size);
1718 if (!mh_strcasecmp (*ap, "server")) {
1722 if (!mh_strcasecmp (*ap, "subject")) {
1723 e->eb_subject = *ep;
1726 if (composing && !mh_strcasecmp (*ap, "body")) {
1727 e->eb_body = getcpy (*ep);
1732 if (!e->eb_access) {
1734 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1735 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1748 InitApplication (CT ct)
1751 CI ci = &ct->c_ctinfo;
1754 for (kv = SubApplication; kv->kv_key; kv++)
1755 if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1757 ct->c_subtype = kv->kv_value;
1764 * Set up structures for placing unencoded
1765 * content when building parts.
1769 init_decoded_content (CT ct)
1773 if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
1774 adios (NULL, "out of memory");
1777 ct->c_ceopenfnx = open7Bit; /* since unencoded */
1778 ct->c_ceclosefnx = close_encoding;
1779 ct->c_cesizefnx = NULL; /* since unencoded */
1786 * TRANSFER ENCODINGS
1790 init_encoding (CT ct, OpenCEFunc openfnx)
1794 if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
1795 adios (NULL, "out of memory");
1798 ct->c_ceopenfnx = openfnx;
1799 ct->c_ceclosefnx = close_encoding;
1800 ct->c_cesizefnx = size_encoding;
1807 close_encoding (CT ct)
1811 if (!(ce = ct->c_cefile))
1821 static unsigned long
1822 size_encoding (CT ct)
1830 if (!(ce = ct->c_cefile))
1831 return (ct->c_end - ct->c_begin);
1833 if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
1834 return (long) st.st_size;
1837 if (stat (ce->ce_file, &st) != NOTOK)
1838 return (long) st.st_size;
1843 if (ct->c_encoding == CE_EXTERNAL)
1844 return (ct->c_end - ct->c_begin);
1847 if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1848 return (ct->c_end - ct->c_begin);
1850 if (fstat (fd, &st) != NOTOK)
1851 size = (long) st.st_size;
1855 (*ct->c_ceclosefnx) (ct);
1864 static unsigned char b642nib[0x80] = {
1865 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1866 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1867 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1868 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1869 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1870 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1871 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1872 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1873 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
1874 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1875 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1876 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1877 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1878 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1879 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1880 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1887 return init_encoding (ct, openBase64);
1892 openBase64 (CT ct, char **file)
1894 int bitno, cc, digested;
1897 unsigned char value, *b, *b1, *b2, *b3;
1898 char *cp, *ep, buffer[BUFSIZ];
1902 b = (unsigned char *) &bits;
1903 b1 = &b[endian > 0 ? 1 : 2];
1904 b2 = &b[endian > 0 ? 2 : 1];
1905 b3 = &b[endian > 0 ? 3 : 0];
1909 fseek (ce->ce_fp, 0L, SEEK_SET);
1914 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1915 content_error (ce->ce_file, ct, "unable to fopen for reading");
1921 if (*file == NULL) {
1922 ce->ce_file = add (m_scratch ("", tmp), NULL);
1925 ce->ce_file = add (*file, NULL);
1929 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1930 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1934 if ((len = ct->c_end - ct->c_begin) < 0)
1935 adios (NULL, "internal error(1)");
1937 if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1938 content_error (ct->c_file, ct, "unable to open for reading");
1942 if ((digested = ct->c_digested))
1943 MD5Init (&mdContext);
1949 lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1951 switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1953 content_error (ct->c_file, ct, "error reading from");
1957 content_error (NULL, ct, "premature eof");
1965 for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1970 if (skip || (*cp & 0x80)
1971 || (value = b642nib[*cp & 0x7f]) > 0x3f) {
1973 fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1975 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1978 content_error (NULL, ct,
1979 "invalid BASE64 encoding -- continuing");
1983 bits |= value << bitno;
1985 if ((bitno -= 6) < 0) {
1986 putc ((char) *b1, ce->ce_fp);
1988 MD5Update (&mdContext, b1, 1);
1990 putc ((char) *b2, ce->ce_fp);
1992 MD5Update (&mdContext, b2, 1);
1994 putc ((char) *b3, ce->ce_fp);
1996 MD5Update (&mdContext, b3, 1);
2000 if (ferror (ce->ce_fp)) {
2001 content_error (ce->ce_file, ct,
2002 "error writing to");
2005 bitno = 18, bits = 0L, skip = 0;
2011 goto self_delimiting;
2020 fprintf (stderr, "premature ending (bitno %d)\n", bitno);
2022 content_error (NULL, ct, "invalid BASE64 encoding");
2027 fseek (ct->c_fp, 0L, SEEK_SET);
2029 if (fflush (ce->ce_fp)) {
2030 content_error (ce->ce_file, ct, "error writing to");
2035 unsigned char digest[16];
2037 MD5Final (digest, &mdContext);
2038 if (memcmp((char *) digest, (char *) ct->c_digest,
2039 sizeof(digest) / sizeof(digest[0])))
2040 content_error (NULL, ct,
2041 "content integrity suspect (digest mismatch) -- continuing");
2044 fprintf (stderr, "content integrity confirmed\n");
2047 fseek (ce->ce_fp, 0L, SEEK_SET);
2050 *file = ce->ce_file;
2051 return fileno (ce->ce_fp);
2054 free_encoding (ct, 0);
2063 static char hex2nib[0x80] = {
2064 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2065 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2066 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2067 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2068 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2069 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2070 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
2071 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2072 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
2073 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2074 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2075 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2076 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
2077 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2078 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2079 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
2086 return init_encoding (ct, openQuoted);
2091 openQuoted (CT ct, char **file)
2093 int cc, digested, len, quoted;
2095 char buffer[BUFSIZ];
2102 fseek (ce->ce_fp, 0L, SEEK_SET);
2107 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2108 content_error (ce->ce_file, ct, "unable to fopen for reading");
2114 if (*file == NULL) {
2115 ce->ce_file = add (m_scratch ("", tmp), NULL);
2118 ce->ce_file = add (*file, NULL);
2122 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2123 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2127 if ((len = ct->c_end - ct->c_begin) < 0)
2128 adios (NULL, "internal error(2)");
2130 if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2131 content_error (ct->c_file, ct, "unable to open for reading");
2135 if ((digested = ct->c_digested))
2136 MD5Init (&mdContext);
2143 fseek (ct->c_fp, ct->c_begin, SEEK_SET);
2147 if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
2148 content_error (NULL, ct, "premature eof");
2152 if ((cc = strlen (buffer)) > len)
2156 for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
2161 for (; cp < ep; cp++) {
2164 if (!isxdigit (*cp)) {
2166 dp = "expecting hexidecimal-digit";
2167 goto invalid_encoding;
2170 mask |= hex2nib[*cp & 0x7f];
2171 putc (mask, ce->ce_fp);
2173 MD5Update (&mdContext, &mask, 1);
2177 putc (*cp, ce->ce_fp);
2179 MD5Update (&mdContext, (unsigned char *) ":", 1);
2183 if (!isxdigit (*cp))
2185 mask = hex2nib[*cp & 0x7f];
2191 if (ferror (ce->ce_fp)) {
2192 content_error (ce->ce_file, ct, "error writing to");
2201 if (*cp < '!' || *cp > '~') {
2203 dp = "expecting character in range [!..~]";
2206 i = strlen (invo_name) + 2;
2207 content_error (NULL, ct,
2208 "invalid QUOTED-PRINTABLE encoding -- %s,\n%*.*sbut got char 0x%x",
2216 putc (*cp, ce->ce_fp);
2219 MD5Update (&mdContext, (unsigned char *) "\r\n",2);
2221 MD5Update (&mdContext, (unsigned char *) cp, 1);
2223 if (ferror (ce->ce_fp)) {
2224 content_error (ce->ce_file, ct, "error writing to");
2230 if (*++cp != '\n') {
2239 content_error (NULL, ct,
2240 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2244 fseek (ct->c_fp, 0L, SEEK_SET);
2246 if (fflush (ce->ce_fp)) {
2247 content_error (ce->ce_file, ct, "error writing to");
2252 unsigned char digest[16];
2254 MD5Final (digest, &mdContext);
2255 if (memcmp((char *) digest, (char *) ct->c_digest,
2256 sizeof(digest) / sizeof(digest[0])))
2257 content_error (NULL, ct,
2258 "content integrity suspect (digest mismatch) -- continuing");
2261 fprintf (stderr, "content integrity confirmed\n");
2264 fseek (ce->ce_fp, 0L, SEEK_SET);
2267 *file = ce->ce_file;
2268 return fileno (ce->ce_fp);
2271 free_encoding (ct, 0);
2283 if (init_encoding (ct, open7Bit) == NOTOK)
2286 ct->c_cesizefnx = NULL; /* no need to decode for real size */
2292 open7Bit (CT ct, char **file)
2295 char buffer[BUFSIZ];
2300 fseek (ce->ce_fp, 0L, SEEK_SET);
2305 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2306 content_error (ce->ce_file, ct, "unable to fopen for reading");
2312 if (*file == NULL) {
2313 ce->ce_file = add (m_scratch ("", tmp), NULL);
2316 ce->ce_file = add (*file, NULL);
2320 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2321 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2325 if (ct->c_type == CT_MULTIPART) {
2327 CI ci = &ct->c_ctinfo;
2330 fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2331 len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2332 + 1 + strlen (ci->ci_subtype);
2333 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
2334 putc (';', ce->ce_fp);
2337 snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
2339 if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
2340 fputs ("\n\t", ce->ce_fp);
2343 putc (' ', ce->ce_fp);
2346 fprintf (ce->ce_fp, "%s", buffer);
2350 if (ci->ci_comment) {
2351 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2352 fputs ("\n\t", ce->ce_fp);
2356 putc (' ', ce->ce_fp);
2359 fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2362 fprintf (ce->ce_fp, "\n");
2364 fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2366 fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2368 fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
2369 fprintf (ce->ce_fp, "\n");
2372 if ((len = ct->c_end - ct->c_begin) < 0)
2373 adios (NULL, "internal error(3)");
2375 if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2376 content_error (ct->c_file, ct, "unable to open for reading");
2380 lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2382 switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2384 content_error (ct->c_file, ct, "error reading from");
2388 content_error (NULL, ct, "premature eof");
2396 fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2397 if (ferror (ce->ce_fp)) {
2398 content_error (ce->ce_file, ct, "error writing to");
2403 fseek (ct->c_fp, 0L, SEEK_SET);
2405 if (fflush (ce->ce_fp)) {
2406 content_error (ce->ce_file, ct, "error writing to");
2410 fseek (ce->ce_fp, 0L, SEEK_SET);
2413 *file = ce->ce_file;
2414 return fileno (ce->ce_fp);
2417 free_encoding (ct, 0);
2427 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2429 char cachefile[BUFSIZ];
2432 fseek (ce->ce_fp, 0L, SEEK_SET);
2437 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2438 content_error (ce->ce_file, ct, "unable to fopen for reading");
2444 if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2445 cachefile, sizeof(cachefile)) != NOTOK) {
2446 if ((ce->ce_fp = fopen (cachefile, "r"))) {
2447 ce->ce_file = getcpy (cachefile);
2451 admonish (cachefile, "unable to fopen for reading");
2458 *file = ce->ce_file;
2459 *fd = fileno (ce->ce_fp);
2470 return init_encoding (ct, openFile);
2475 openFile (CT ct, char **file)
2478 char cachefile[BUFSIZ];
2479 struct exbody *e = ct->c_ctexbody;
2480 CE ce = ct->c_cefile;
2482 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2494 content_error (NULL, ct, "missing name parameter");
2498 ce->ce_file = getcpy (e->eb_name);
2501 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2502 content_error (ce->ce_file, ct, "unable to fopen for reading");
2506 if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2507 && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2508 cachefile, sizeof(cachefile)) != NOTOK) {
2512 mask = umask (cachetype ? ~m_gmprot () : 0222);
2513 if ((fp = fopen (cachefile, "w"))) {
2515 char buffer[BUFSIZ];
2516 FILE *gp = ce->ce_fp;
2518 fseek (gp, 0L, SEEK_SET);
2520 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2522 fwrite (buffer, sizeof(*buffer), cc, fp);
2526 admonish (ce->ce_file, "error reading");
2531 admonish (cachefile, "error writing");
2539 fseek (ce->ce_fp, 0L, SEEK_SET);
2540 *file = ce->ce_file;
2541 return fileno (ce->ce_fp);
2551 return init_encoding (ct, openFTP);
2556 openFTP (CT ct, char **file)
2558 int cachetype, caching, fd;
2560 char *bp, *ftp, *user, *pass;
2561 char buffer[BUFSIZ], cachefile[BUFSIZ];
2564 static char *username = NULL;
2565 static char *password = NULL;
2570 if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2578 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2589 if (!e->eb_name || !e->eb_site) {
2590 content_error (NULL, ct, "missing %s parameter",
2591 e->eb_name ? "site": "name");
2598 pidcheck (pidwait (xpid, NOTOK));
2602 /* Get the buffer ready to go */
2604 buflen = sizeof(buffer);
2607 * Construct the query message for user
2609 snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2615 snprintf (bp, buflen, " (content %s)", e->eb_partno);
2621 snprintf (bp, buflen, "\n using %sFTP from site %s",
2622 e->eb_flags ? "anonymous " : "", e->eb_site);
2627 if (e->eb_size > 0) {
2628 snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2633 snprintf (bp, buflen, "? ");
2636 * Now, check the answer
2638 if (!getanswer (buffer))
2643 snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ());
2646 ruserpass (e->eb_site, &username, &password);
2651 ce->ce_unlink = (*file == NULL);
2653 cachefile[0] = '\0';
2654 if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2655 && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2656 cachefile, sizeof(cachefile)) != NOTOK) {
2657 if (*file == NULL) {
2664 ce->ce_file = add (*file, NULL);
2666 ce->ce_file = add (cachefile, NULL);
2668 ce->ce_file = add (m_scratch ("", tmp), NULL);
2670 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2671 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2679 int child_id, i, vecp;
2683 vec[vecp++] = r1bindex (ftp, '/');
2684 vec[vecp++] = e->eb_site;
2687 vec[vecp++] = e->eb_dir;
2688 vec[vecp++] = e->eb_name;
2689 vec[vecp++] = ce->ce_file,
2690 vec[vecp++] = e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii")
2691 ? "ascii" : "binary";
2696 for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2700 adios ("fork", "unable to");
2704 close (fileno (ce->ce_fp));
2706 fprintf (stderr, "unable to exec ");
2712 if (pidXwait (child_id, NULL)) {
2716 username = password = NULL;
2725 if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name,
2727 e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii"), 0)
2734 chmod (cachefile, cachetype ? m_gmprot () : 0444);
2739 mask = umask (cachetype ? ~m_gmprot () : 0222);
2740 if ((fp = fopen (cachefile, "w"))) {
2742 FILE *gp = ce->ce_fp;
2744 fseek (gp, 0L, SEEK_SET);
2746 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2748 fwrite (buffer, sizeof(*buffer), cc, fp);
2752 admonish (ce->ce_file, "error reading");
2757 admonish (cachefile, "error writing");
2766 fseek (ce->ce_fp, 0L, SEEK_SET);
2767 *file = ce->ce_file;
2768 return fileno (ce->ce_fp);
2779 return init_encoding (ct, openMail);
2784 openMail (CT ct, char **file)
2786 int child_id, fd, i, vecp;
2788 char *bp, buffer[BUFSIZ], *vec[7];
2789 struct exbody *e = ct->c_ctexbody;
2790 CE ce = ct->c_cefile;
2792 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2803 if (!e->eb_server) {
2804 content_error (NULL, ct, "missing server parameter");
2811 pidcheck (pidwait (xpid, NOTOK));
2815 /* Get buffer ready to go */
2817 buflen = sizeof(buffer);
2819 /* Now construct query message */
2820 snprintf (bp, buflen, "Retrieve content");
2826 snprintf (bp, buflen, " %s", e->eb_partno);
2832 snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2834 e->eb_subject ? e->eb_subject : e->eb_body);
2836 /* Now, check answer */
2837 if (!getanswer (buffer))
2841 vec[vecp++] = r1bindex (mailproc, '/');
2842 vec[vecp++] = e->eb_server;
2843 vec[vecp++] = "-subject";
2844 vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2845 vec[vecp++] = "-body";
2846 vec[vecp++] = e->eb_body;
2849 for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2853 advise ("fork", "unable to");
2857 execvp (mailproc, vec);
2858 fprintf (stderr, "unable to exec ");
2864 if (pidXwait (child_id, NULL) == OK)
2865 advise (NULL, "request sent");
2869 if (*file == NULL) {
2870 ce->ce_file = add (m_scratch ("", tmp), NULL);
2873 ce->ce_file = add (*file, NULL);
2877 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2878 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2882 fseek (ce->ce_fp, 0L, SEEK_SET);
2883 *file = ce->ce_file;
2884 return fileno (ce->ce_fp);
2889 fgetstr (char *s, int n, FILE *stream)
2893 for (ep = (cp = s) + n; cp < ep; ) {
2896 if (!fgets (cp, n, stream))
2897 return (cp != s ? s : NULL);
2898 if (cp == s && *cp != '#')
2901 cp += (i = strlen (cp)) - 1;
2902 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
2913 * Parse the composition draft for text and directives.
2914 * Do initial setup of Content structure.
2918 user_content (FILE *in, char *file, char *buf, CT *ctp)
2922 char buffer[BUFSIZ];
2923 struct multipart *m;
2926 struct str2init *s2i;
2931 if (buf[0] == '\n' || strcmp (buf, "#\n") == 0) {
2936 /* allocate basic Content structure */
2937 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
2938 adios (NULL, "out of memory");
2941 /* allocate basic structure for handling decoded content */
2942 init_decoded_content (ct);
2949 * Handle inline text. Check if line
2950 * is one of the following forms:
2952 * 1) doesn't begin with '#' (implicit directive)
2953 * 2) begins with "##" (implicit directive)
2954 * 3) begins with "#<"
2956 if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
2960 char content[BUFSIZ];
2963 /* use a temp file to collect the plain text lines */
2964 ce->ce_file = add (m_tmpfil (invo_name), NULL);
2967 if ((out = fopen (ce->ce_file, "w")) == NULL)
2968 adios (ce->ce_file, "unable to open for writing");
2970 if (buf[0] == '#' && buf[1] == '<') {
2971 strncpy (content, buf + 2, sizeof(content));
2978 /* the directive is implicit */
2979 strncpy (content, "text/plain", sizeof(content));
2981 strncpy (buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer));
2985 if (headers >= 0 && uprf (buffer, DESCR_FIELD)
2986 && buffer[i = strlen (DESCR_FIELD)] == ':') {
2990 ct->c_descr = add (buffer + i + 1, ct->c_descr);
2991 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
2992 adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
2993 switch (buffer[0]) {
3000 adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
3008 if (headers >= 0 && uprf (buffer, DISPO_FIELD)
3009 && buffer[i = strlen (DISPO_FIELD)] == ':') {
3013 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
3014 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
3015 adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
3016 switch (buffer[0]) {
3023 adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
3031 if (headers != 1 || buffer[0] != '\n')
3032 fputs (buffer, out);
3037 if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
3039 if (buffer[0] == '#') {
3042 if (buffer[1] != '#')
3044 for (cp = (bp = buffer) + 1; *cp; cp++)
3051 ct->c_end = ftell (out);
3054 /* parse content type */
3055 if (get_ctinfo (content, ct, inlineD) == NOTOK)
3058 for (s2i = str2cts; s2i->si_key; s2i++)
3059 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
3061 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
3065 * check type specified (possibly implicitly)
3067 switch (ct->c_type = s2i->si_val) {
3069 if (!mh_strcasecmp (ci->ci_subtype, "rfc822")) {
3070 ct->c_encoding = CE_7BIT;
3075 adios (NULL, "it doesn't make sense to define an in-line %s content",
3076 ct->c_type == CT_MESSAGE ? "message" : "multipart");
3081 if ((ct->c_ctinitfnx = s2i->si_init))
3082 (*ct->c_ctinitfnx) (ct);
3087 fseek (in, pos, SEEK_SET);
3092 * If we've reached this point, the next line
3093 * must be some type of explicit directive.
3096 /* check if directive is external-type */
3097 extrnal = (buf[1] == '@');
3099 /* parse directive */
3100 if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
3103 /* check directive against the list of MIME types */
3104 for (s2i = str2cts; s2i->si_key; s2i++)
3105 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
3109 * Check if the directive specified a valid type.
3110 * This will happen if it was one of the following forms:
3112 * #type/subtype (or)
3116 if (!ci->ci_subtype)
3117 adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
3119 switch (ct->c_type = s2i->si_val) {
3121 adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
3122 ci->ci_type, ci->ci_subtype);
3126 if (!mh_strcasecmp (ci->ci_subtype, "partial"))
3127 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
3128 ci->ci_type, ci->ci_subtype);
3129 if (!mh_strcasecmp (ci->ci_subtype, "external-body"))
3130 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
3131 ci->ci_type, ci->ci_subtype);
3134 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
3135 ci->ci_type, ci->ci_subtype);
3139 if ((ct->c_ctinitfnx = s2i->si_init))
3140 (*ct->c_ctinitfnx) (ct);
3145 * #@type/subtype (external types directive)
3152 adios (NULL, "need external information for \"#@%s/%s\"",
3153 ci->ci_type, ci->ci_subtype);
3156 snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
3157 free (ci->ci_magic);
3158 ci->ci_magic = NULL;
3161 * Since we are using the current Content structure to
3162 * hold information about the type of the external
3163 * reference, we need to create another Content structure
3164 * for the message/external-body to wrap it in.
3166 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
3167 adios (NULL, "out of memory");
3170 if (get_ctinfo (buffer, ct, 0) == NOTOK)
3172 ct->c_type = CT_MESSAGE;
3173 ct->c_subtype = MESSAGE_EXTERNAL;
3175 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
3176 adios (NULL, "out of memory");
3177 ct->c_ctparams = (void *) e;
3183 if (params_external (ct, 1) == NOTOK)
3189 /* Handle [file] argument */
3191 /* check if specifies command to execute */
3192 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
3193 for (cp = ci->ci_magic + 1; isspace (*cp); cp++)
3196 adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
3197 cp = add (cp, NULL);
3198 free (ci->ci_magic);
3201 /* record filename of decoded contents */
3202 ce->ce_file = ci->ci_magic;
3203 if (access (ce->ce_file, R_OK) == NOTOK)
3204 adios ("reading", "unable to access %s for", ce->ce_file);
3205 if (listsw && stat (ce->ce_file, &st) != NOTOK)
3206 ct->c_end = (long) st.st_size;
3207 ci->ci_magic = NULL;
3213 * No [file] argument, so check profile for
3214 * method to compose content.
3216 snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
3217 invo_name, ci->ci_type, ci->ci_subtype);
3218 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
3219 snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type);
3220 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
3221 content_error (NULL, ct, "don't know how to compose content");
3225 ci->ci_magic = add (cp, NULL);
3230 adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
3234 * #forw [+folder] [msgs]
3236 if (!mh_strcasecmp (ci->ci_type, "forw")) {
3238 char *folder, *arguments[MAXARGS];
3242 ap = brkstring (ci->ci_magic, " ", "\n");
3243 copyip (ap, arguments, MAXARGS);
3245 arguments[0] = "cur";
3246 arguments[1] = NULL;
3250 /* search the arguments for a folder name */
3251 for (ap = arguments; *ap; ap++) {
3253 if (*cp == '+' || *cp == '@') {
3255 adios (NULL, "only one folder per #forw directive");
3257 folder = pluspath (cp);
3261 /* else, use the current folder */
3263 folder = add (getfolder (1), NULL);
3265 if (!(mp = folder_read (folder)))
3266 adios (NULL, "unable to read folder %s", folder);
3267 for (ap = arguments; *ap; ap++) {
3269 if (*cp != '+' && *cp != '@')
3270 if (!m_convert (mp, cp))
3277 * If there is more than one message to include, make this
3278 * a content of type "multipart/digest" and insert each message
3279 * as a subpart. If there is only one message, then make this
3280 * a content of type "message/rfc822".
3282 if (mp->numsel > 1) {
3283 /* we are forwarding multiple messages */
3284 if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
3286 ct->c_type = CT_MULTIPART;
3287 ct->c_subtype = MULTI_DIGEST;
3289 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
3290 adios (NULL, "out of memory");
3291 ct->c_ctparams = (void *) m;
3294 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
3295 if (is_selected(mp, msgnum)) {
3300 if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
3301 adios (NULL, "out of memory");
3302 init_decoded_content (p);
3304 if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
3306 p->c_type = CT_MESSAGE;
3307 p->c_subtype = MESSAGE_RFC822;
3309 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
3310 pe->ce_file = add (buffer, NULL);
3311 if (listsw && stat (pe->ce_file, &st) != NOTOK)
3312 p->c_end = (long) st.st_size;
3314 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
3315 adios (NULL, "out of memory");
3317 pp = &part->mp_next;
3322 /* we are forwarding one message */
3323 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
3325 ct->c_type = CT_MESSAGE;
3326 ct->c_subtype = MESSAGE_RFC822;
3328 msgnum = mp->lowsel;
3329 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
3330 ce->ce_file = add (buffer, NULL);
3331 if (listsw && stat (ce->ce_file, &st) != NOTOK)
3332 ct->c_end = (long) st.st_size;
3335 folder_free (mp); /* free folder/message structure */
3342 if (!mh_strcasecmp (ci->ci_type, "end")) {
3349 * #begin [ alternative | parallel ]
3351 if (!mh_strcasecmp (ci->ci_type, "begin")) {
3352 if (!ci->ci_magic) {
3354 cp = SubMultiPart[vrsn - 1].kv_key;
3355 } else if (!mh_strcasecmp (ci->ci_magic, "alternative")) {
3356 vrsn = MULTI_ALTERNATE;
3357 cp = SubMultiPart[vrsn - 1].kv_key;
3358 } else if (!mh_strcasecmp (ci->ci_magic, "parallel")) {
3359 vrsn = MULTI_PARALLEL;
3360 cp = SubMultiPart[vrsn - 1].kv_key;
3361 } else if (uprf (ci->ci_magic, "digest")) {
3364 vrsn = MULTI_UNKNOWN;
3369 snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
3370 if (get_ctinfo (buffer, ct, 0) == NOTOK)
3372 ct->c_type = CT_MULTIPART;
3373 ct->c_subtype = vrsn;
3375 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
3376 adios (NULL, "out of memory");
3377 ct->c_ctparams = (void *) m;
3380 while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
3384 if (user_content (in, file, buffer, &p) == DONE) {
3386 adios (NULL, "empty \"#begin ... #end\" sequence");
3392 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
3393 adios (NULL, "out of memory");
3395 pp = &part->mp_next;
3398 admonish (NULL, "premature end-of-file, missing #end");
3405 adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
3406 return NOTOK; /* NOT REACHED */
3411 set_id (CT ct, int top)
3415 static time_t clock = 0;
3416 static char *msgfmt;
3420 snprintf (msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
3421 (int) getpid(), (long) clock, LocalName());
3423 msgfmt = getcpy(msgid);
3425 snprintf (msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
3426 ct->c_id = getcpy (msgid);
3430 static char ebcdicsafe[0x100] = {
3431 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3432 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
3433 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3434 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3435 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
3436 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3437 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3438 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3439 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3440 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3441 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3442 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
3443 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3444 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3445 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
3446 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
3447 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3448 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3449 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3450 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3451 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3452 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3453 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3454 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3455 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3456 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3457 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3458 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3459 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3460 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3461 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3462 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
3467 * Fill out, or expand the various contents in the composition
3468 * draft. Read-in any necessary files. Parse and execute any
3469 * commands specified by profile composition strings.
3473 compose_content (CT ct)
3475 CE ce = ct->c_cefile;
3477 switch (ct->c_type) {
3482 char partnam[BUFSIZ];
3483 struct multipart *m = (struct multipart *) ct->c_ctparams;
3487 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
3488 pp = partnam + strlen (partnam);
3493 /* first, we call compose_content on all the subparts */
3494 for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
3495 CT p = part->mp_part;
3497 sprintf (pp, "%d", partnum);
3498 p->c_partno = add (partnam, NULL);
3499 if (compose_content (p) == NOTOK)
3504 * If the -rfc934mode switch is given, then check all
3505 * the subparts of a multipart/digest. If they are all
3506 * message/rfc822, then mark this content and all
3507 * subparts with the rfc934 compatibility mode flag.
3509 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
3512 for (part = m->mp_parts; part; part = part->mp_next) {
3513 CT p = part->mp_part;
3515 if (p->c_subtype != MESSAGE_RFC822) {
3520 ct->c_rfc934 = is934;
3521 for (part = m->mp_parts; part; part = part->mp_next) {
3522 CT p = part->mp_part;
3524 if ((p->c_rfc934 = is934))
3530 ct->c_end = (partnum = strlen (prefix) + 2) + 2;
3534 for (part = m->mp_parts; part; part = part->mp_next)
3535 ct->c_end += part->mp_part->c_end + partnum;
3541 /* Nothing to do for type message */
3545 * Discrete types (text/application/audio/image/video)
3550 int i, xstdout, len, buflen;
3551 char *bp, **ap, *cp;
3552 char *vec[4], buffer[BUFSIZ];
3554 CI ci = &ct->c_ctinfo;
3556 if (!(cp = ci->ci_magic))
3557 adios (NULL, "internal error(5)");
3559 ce->ce_file = add (m_tmpfil (invo_name), NULL);
3564 /* Get buffer ready to go */
3567 buflen = sizeof(buffer);
3570 * Parse composition string into buffer
3572 for ( ; *cp; cp++) {
3577 /* insert parameters from directive */
3581 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
3582 snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
3592 /* %f, and stdout is not-redirected */
3598 * insert temporary filename where
3599 * content should be written
3601 snprintf (bp, buflen, "%s", ce->ce_file);
3605 /* insert content subtype */
3606 strncpy (bp, ci->ci_subtype, buflen);
3610 /* insert character % */
3631 printf ("composing content %s/%s from command\n\t%s\n",
3632 ci->ci_type, ci->ci_subtype, buffer);
3634 fflush (stdout); /* not sure if need for -noverbose */
3641 if ((out = fopen (ce->ce_file, "w")) == NULL)
3642 adios (ce->ce_file, "unable to open for writing");
3644 for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++)
3648 adios ("fork", "unable to fork");
3653 dup2 (fileno (out), 1);
3654 close (fileno (out));
3655 execvp ("/bin/sh", vec);
3656 fprintf (stderr, "unable to exec ");
3663 if (pidXwait(child_id, NULL))
3669 /* Check size of file */
3670 if (listsw && ct->c_end == 0L) {
3673 if (stat (ce->ce_file, &st) != NOTOK)
3674 ct->c_end = (long) st.st_size;
3686 * 1) choose a transfer encoding.
3687 * 2) check for clashes with multipart boundary string.
3688 * 3) for text content, figure out which character set is being used.
3690 * If there is a clash with one of the contents and the multipart boundary,
3691 * this function will exit with NOTOK. This will cause the scanning process
3692 * to be repeated with a different multipart boundary. It is possible
3693 * (although highly unlikely) that this scan will be repeated multiple times.
3697 scan_content (CT ct)
3700 int check8bit, contains8bit = 0; /* check if contains 8bit data */
3701 int checklinelen, linelen = 0; /* check for long lines */
3702 int checkboundary, boundaryclash = 0; /* check if clashes with multipart boundary */
3703 int checklinespace, linespace = 0; /* check if any line ends with space */
3704 int checkebcdic, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
3705 char *cp, buffer[BUFSIZ];
3708 CE ce = ct->c_cefile;
3711 * handle multipart by scanning all subparts
3712 * and then checking their encoding.
3714 if (ct->c_type == CT_MULTIPART) {
3715 struct multipart *m = (struct multipart *) ct->c_ctparams;
3718 /* initially mark the domain of enclosing multipart as 7bit */
3719 ct->c_encoding = CE_7BIT;
3721 for (part = m->mp_parts; part; part = part->mp_next) {
3722 CT p = part->mp_part;
3724 if (scan_content (p) == NOTOK) /* choose encoding for subpart */
3727 /* if necessary, enlarge encoding for enclosing multipart */
3728 if (p->c_encoding == CE_BINARY)
3729 ct->c_encoding = CE_BINARY;
3730 if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
3731 ct->c_encoding = CE_8BIT;
3738 * Decide what to check while scanning this content.
3740 switch (ct->c_type) {
3744 if (ct->c_subtype == TEXT_PLAIN) {
3749 checkebcdic = ebcdicsw;
3755 case CT_APPLICATION:
3757 checkebcdic = ebcdicsw;
3769 /* don't check anything for message/external */
3770 if (ct->c_subtype == MESSAGE_EXTERNAL)
3780 * Don't check anything for these types,
3781 * since we are forcing use of base64.
3792 * Scan the unencoded content
3794 if (check8bit || checklinelen || checklinespace || checkboundary) {
3795 if ((in = fopen (ce->ce_file, "r")) == NULL)
3796 adios (ce->ce_file, "unable to open for reading");
3797 len = strlen (prefix);
3799 while (fgets (buffer, sizeof(buffer) - 1, in)) {
3801 * Check for 8bit data.
3804 for (cp = buffer; *cp; cp++) {
3805 if (!isascii (*cp)) {
3807 check8bit = 0; /* no need to keep checking */
3810 * Check if character is ebcdic-safe. We only check
3811 * this if also checking for 8bit data.
3813 if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
3815 checkebcdic = 0; /* no need to keep checking */
3821 * Check line length.
3823 if (checklinelen && (strlen (buffer) > CPERLIN + 1)) {
3825 checklinelen = 0; /* no need to keep checking */
3829 * Check if line ends with a space.
3831 if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace (*cp)) {
3833 checklinespace = 0; /* no need to keep checking */
3837 * Check if content contains a line that clashes
3838 * with our standard boundary for multipart messages.
3840 if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
3841 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
3845 if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) {
3847 checkboundary = 0; /* no need to keep checking */
3855 * Decide which transfer encoding to use.
3857 switch (ct->c_type) {
3860 * If the text content didn't specify a character
3861 * set, we need to figure out which one was used.
3863 t = (struct text *) ct->c_ctparams;
3864 if (t->tx_charset == CHARSET_UNSPECIFIED) {
3865 CI ci = &ct->c_ctinfo;
3868 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
3872 t->tx_charset = CHARSET_UNKNOWN;
3873 *ap = concat ("charset=", write_charset_8bit(), NULL);
3875 t->tx_charset = CHARSET_USASCII;
3876 *ap = add ("charset=us-ascii", NULL);
3879 cp = strchr(*ap++, '=');
3885 if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
3886 ct->c_encoding = CE_QUOTED;
3888 ct->c_encoding = CE_7BIT;
3891 case CT_APPLICATION:
3892 /* For application type, use base64, except when postscript */
3893 if (contains8bit || ebcdicunsafe || linelen || linespace || checksw)
3894 ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
3895 ? CE_QUOTED : CE_BASE64;
3897 ct->c_encoding = CE_7BIT;
3901 ct->c_encoding = CE_7BIT;
3907 /* For audio, image, and video contents, just use base64 */
3908 ct->c_encoding = CE_BASE64;
3912 return (boundaryclash ? NOTOK : OK);
3917 * Scan the content structures, and build header
3918 * fields that will need to be output into the
3923 build_headers (CT ct)
3925 int cc, mailbody, len;
3927 char *np, *vp, buffer[BUFSIZ];
3928 CI ci = &ct->c_ctinfo;
3931 * If message is type multipart, then add the multipart
3932 * boundary to the list of attribute/value pairs.
3934 if (ct->c_type == CT_MULTIPART) {
3936 static int level = 0; /* store nesting level */
3940 snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++);
3941 cp = strchr(*ap++ = add (buffer, NULL), '=');
3948 * Skip the output of Content-Type, parameters, content
3949 * description and disposition, and Content-ID if the
3950 * content is of type "message" and the rfc934 compatibility
3951 * flag is set (which means we are inside multipart/digest
3952 * and the switch -rfc934mode was given).
3954 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
3958 * output the content type and subtype
3960 np = add (TYPE_FIELD, NULL);
3961 vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
3963 /* keep track of length of line */
3964 len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
3965 + strlen (ci->ci_subtype) + 3;
3967 mailbody = ct->c_type == CT_MESSAGE
3968 && ct->c_subtype == MESSAGE_EXTERNAL
3969 && ((struct exbody *) ct->c_ctparams)->eb_body;
3972 * Append the attribute/value pairs to
3973 * the end of the Content-Type line.
3975 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
3976 if (mailbody && !mh_strcasecmp (*ap, "body"))
3982 snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
3983 if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
3984 vp = add ("\n\t", vp);
3990 vp = add (buffer, vp);
3995 * Append any RFC-822 comment to the end of
3996 * the Content-Type line.
3998 if (ci->ci_comment) {
3999 snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
4000 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
4001 vp = add ("\n\t", vp);
4007 vp = add (buffer, vp);
4010 vp = add ("\n", vp);
4011 add_header (ct, np, vp);
4014 * output the Content-ID, unless disabled by -nocontentid
4016 if (contentidsw && ct->c_id) {
4017 np = add (ID_FIELD, NULL);
4018 vp = concat (" ", ct->c_id, NULL);
4019 add_header (ct, np, vp);
4023 * output the Content-Description
4026 np = add (DESCR_FIELD, NULL);
4027 vp = concat (" ", ct->c_descr, NULL);
4028 add_header (ct, np, vp);
4032 * output the Content-Disposition
4035 np = add (DISPO_FIELD, NULL);
4036 vp = concat (" ", ct->c_dispo, NULL);
4037 add_header (ct, np, vp);
4042 * If this is the internal content structure for a
4043 * "message/external", then we are done with the
4044 * headers (since it has no body).
4050 * output the Content-MD5
4053 np = add (MD5_FIELD, NULL);
4054 vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
4055 add_header (ct, np, vp);
4059 * output the Content-Transfer-Encoding
4061 switch (ct->c_encoding) {
4063 /* Nothing to output */
4065 np = add (ENCODING_FIELD, NULL);
4066 vp = concat (" ", "7bit", "\n", NULL);
4067 add_header (ct, np, vp);
4072 if (ct->c_type == CT_MESSAGE)
4073 adios (NULL, "internal error, invalid encoding");
4075 np = add (ENCODING_FIELD, NULL);
4076 vp = concat (" ", "8bit", "\n", NULL);
4077 add_header (ct, np, vp);
4081 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
4082 adios (NULL, "internal error, invalid encoding");
4084 np = add (ENCODING_FIELD, NULL);
4085 vp = concat (" ", "quoted-printable", "\n", NULL);
4086 add_header (ct, np, vp);
4090 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
4091 adios (NULL, "internal error, invalid encoding");
4093 np = add (ENCODING_FIELD, NULL);
4094 vp = concat (" ", "base64", "\n", NULL);
4095 add_header (ct, np, vp);
4099 if (ct->c_type == CT_MESSAGE)
4100 adios (NULL, "internal error, invalid encoding");
4102 np = add (ENCODING_FIELD, NULL);
4103 vp = concat (" ", "binary", "\n", NULL);
4104 add_header (ct, np, vp);
4108 adios (NULL, "unknown transfer encoding in content");
4113 * Additional content specific header processing
4115 switch (ct->c_type) {
4118 struct multipart *m;
4121 m = (struct multipart *) ct->c_ctparams;
4122 for (part = m->mp_parts; part; part = part->mp_next) {
4132 if (ct->c_subtype == MESSAGE_EXTERNAL) {
4135 e = (struct exbody *) ct->c_ctparams;
4136 build_headers (e->eb_content);
4149 static char nib2b64[0x40+1] =
4150 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4153 calculate_digest (CT ct, int asciiP)
4156 char buffer[BUFSIZ], *vp, *op;
4158 unsigned char digest[16];
4159 unsigned char outbuf[25];
4162 CE ce = ct->c_cefile;
4165 if ((in = fopen (ce->ce_file, "r")) == NULL)
4166 adios (ce->ce_file, "unable to open for reading");
4168 /* Initialize md5 context */
4169 MD5Init (&mdContext);
4171 /* calculate md5 message digest */
4173 while (fgets (buffer, sizeof(buffer) - 1, in)) {
4176 cp = buffer + strlen (buffer) - 1;
4177 if ((c = *cp) == '\n')
4180 MD5Update (&mdContext, (unsigned char *) buffer,
4181 (unsigned int) strlen (buffer));
4184 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
4187 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
4188 MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
4191 /* md5 finalization. Write digest and zero md5 context */
4192 MD5Final (digest, &mdContext);
4197 /* print debugging info */
4201 fprintf (stderr, "MD5 digest=");
4202 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
4204 fprintf (stderr, "%02x", *dp & 0xff);
4205 fprintf (stderr, "\n");
4208 /* encode the digest using base64 */
4209 for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]);
4210 cc > 0; cc -= 3, op += 4) {
4214 bits = (*dp++ & 0xff) << 16;
4216 bits |= (*dp++ & 0xff) << 8;
4218 bits |= *dp++ & 0xff;
4221 for (bp = op + 4; bp > op; bits >>= 6)
4222 *--bp = nib2b64[bits & 0x3f];
4230 /* null terminate string */
4233 /* now make copy and return string */
4234 vp = concat (" ", outbuf, "\n", NULL);
4240 readDigest (CT ct, char *cp)
4245 unsigned char *dp, value, *ep;
4246 unsigned char *b, *b1, *b2, *b3;
4248 b = (unsigned char *) &bits,
4249 b1 = &b[endian > 0 ? 1 : 2],
4250 b2 = &b[endian > 0 ? 2 : 1],
4251 b3 = &b[endian > 0 ? 3 : 0];
4256 for (ep = (dp = ct->c_digest)
4257 + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
4262 || (value = b642nib[*cp & 0x7f]) > 0x3f) {
4264 fprintf (stderr, "invalid BASE64 encoding\n");
4268 bits |= value << bitno;
4270 if ((bitno -= 6) < 0) {
4271 if (dp + (3 - skip) > ep)
4272 goto invalid_digest;
4287 goto self_delimiting;
4292 fprintf (stderr, "premature ending (bitno %d)\n", bitno);
4302 fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
4310 fprintf (stderr, "MD5 digest=");
4311 for (dp = ct->c_digest; dp < ep; dp++)
4312 fprintf (stderr, "%02x", *dp & 0xff);
4313 fprintf (stderr, "\n");
4320 /* Make sure that buf contains at least one appearance of name,
4321 followed by =. If not, insert both name and value, just after
4322 first semicolon, if any. Note that name should not contain a
4323 trailing =. And quotes will be added around the value. Typical
4324 usage: make sure that a Content-Disposition header contains
4325 filename="foo". If it doesn't and value does, use value from
4328 incl_name_value (char *buf, char *name, char *value) {
4331 /* Assume that name is non-null. */
4333 char *name_plus_equal = concat (name, "=", NULL);
4335 if (! strstr (buf, name_plus_equal)) {
4338 char *prefix, *suffix;
4340 /* Trim trailing space, esp. newline. */
4341 for (cp = &buf[strlen (buf) - 1];
4342 cp >= buf && isspace (*cp);
4347 insertion = concat ("; ", name, "=", "\"", value, "\"", NULL);
4349 /* Insert at first semicolon, if any. If none, append to
4351 prefix = add (buf, NULL);
4352 if ((cp = strchr (prefix, ';'))) {
4353 suffix = concat (cp, NULL);
4355 newbuf = concat (prefix, insertion, suffix, "\n", NULL);
4358 /* Append to end. */
4359 newbuf = concat (buf, insertion, "\n", NULL);
4367 free (name_plus_equal);
4374 /* Extract just name_suffix="foo", if any, from value. If there isn't
4375 one, return the entire value. Note that, for example, a name_suffix
4376 of name will match filename="foo", and return foo. */
4378 extract_name_value (char *name_suffix, char *value) {
4379 char *extracted_name_value = value;
4380 char *name_suffix_plus_quote = concat (name_suffix, "=\"", NULL);
4381 char *name_suffix_equals = strstr (value, name_suffix_plus_quote);
4384 free (name_suffix_plus_quote);
4385 if (name_suffix_equals) {
4386 char *name_suffix_begin;
4388 /* Find first \". */
4389 for (cp = name_suffix_equals; *cp != '"'; ++cp) /* empty */;
4390 name_suffix_begin = ++cp;
4391 /* Find second \". */
4392 for (; *cp != '"'; ++cp) /* empty */;
4394 extracted_name_value = mh_xmalloc (cp - name_suffix_begin + 1);
4395 memcpy (extracted_name_value,
4397 cp - name_suffix_begin);
4398 extracted_name_value[cp - name_suffix_begin] = '\0';
4401 return extracted_name_value;