1 /* mhparse.c -- routines to parse the contents of MIME messages
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
14 #include <h/mhparse.h>
16 #include <h/mhcachesbr.h>
17 #include "../sbr/m_mktemp.h"
21 #endif /* HAVE_ICONV */
26 int checksw
= 0; /* check Content-MD5 field */
29 * These are for mhfixmsg to:
30 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
32 * 2) Suppress the warning about bogus multipart content, and report it.
33 * 3) Suppress the warning about extraneous trailing ';' in header parameter
36 int skip_mp_cte_check
;
37 int suppress_bogus_mp_content_warning
;
39 int suppress_extraneous_trailing_semicolon_warning
;
42 * By default, suppress warning about multiple MIME-Version header fields.
44 int suppress_multiple_mime_version_warning
= 1;
46 /* list of preferred type/subtype pairs, for -prefer */
47 char *preferred_types
[NPREFS
],
48 *preferred_subtypes
[NPREFS
];
53 * Structures for TEXT messages
55 struct k2v SubText
[] = {
56 { "plain", TEXT_PLAIN
},
57 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
58 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
59 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
62 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
65 * Structures for MULTIPART messages
67 struct k2v SubMultiPart
[] = {
68 { "mixed", MULTI_MIXED
},
69 { "alternative", MULTI_ALTERNATE
},
70 { "digest", MULTI_DIGEST
},
71 { "parallel", MULTI_PARALLEL
},
72 { "related", MULTI_RELATED
},
73 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
77 * Structures for MESSAGE messages
79 struct k2v SubMessage
[] = {
80 { "rfc822", MESSAGE_RFC822
},
81 { "partial", MESSAGE_PARTIAL
},
82 { "external-body", MESSAGE_EXTERNAL
},
83 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
87 * Structure for APPLICATION messages
89 struct k2v SubApplication
[] = {
90 { "octet-stream", APPLICATION_OCTETS
},
91 { "postscript", APPLICATION_POSTSCRIPT
},
92 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
96 * Mapping of names of CTE types in mhbuild directives
98 static struct k2v EncodingType
[] = {
101 { "q-p", CE_QUOTED
},
102 { "quoted-printable", CE_QUOTED
},
103 { "b64", CE_BASE64
},
104 { "base64", CE_BASE64
},
111 int type_ok (CT
, int);
112 void content_error (char *, CT
, char *, ...);
117 static CT
get_content (FILE *, char *, int);
118 static int get_comment (const char *, const char *, char **, char **);
120 static int InitGeneric (CT
);
121 static int InitText (CT
);
122 static int InitMultiPart (CT
);
123 static void reverse_parts (CT
);
124 static void prefer_parts(CT ct
);
125 static int InitMessage (CT
);
126 static int InitApplication (CT
);
127 static int init_encoding (CT
, OpenCEFunc
);
128 static unsigned long size_encoding (CT
);
129 static int InitBase64 (CT
);
130 static int openBase64 (CT
, char **);
131 static int InitQuoted (CT
);
132 static int openQuoted (CT
, char **);
133 static int Init7Bit (CT
);
134 static int openExternal (CT
, CT
, CE
, char **, int *);
135 static int InitFile (CT
);
136 static int openFile (CT
, char **);
137 static int InitFTP (CT
);
138 static int openFTP (CT
, char **);
139 static int InitMail (CT
);
140 static int openMail (CT
, char **);
141 static int readDigest (CT
, char *);
142 static int get_leftover_mp_content (CT
, int);
143 static int InitURL (CT
);
144 static int openURL (CT
, char **);
145 static int parse_header_attrs (const char *, const char *, char **, PM
*,
147 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
148 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
149 static int get_dispo (char *, CT
, int);
151 struct str2init str2cts
[] = {
152 { "application", CT_APPLICATION
, InitApplication
},
153 { "audio", CT_AUDIO
, InitGeneric
},
154 { "image", CT_IMAGE
, InitGeneric
},
155 { "message", CT_MESSAGE
, InitMessage
},
156 { "multipart", CT_MULTIPART
, InitMultiPart
},
157 { "text", CT_TEXT
, InitText
},
158 { "video", CT_VIDEO
, InitGeneric
},
159 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
160 { NULL
, CT_UNKNOWN
, NULL
},
163 struct str2init str2ces
[] = {
164 { "base64", CE_BASE64
, InitBase64
},
165 { "quoted-printable", CE_QUOTED
, InitQuoted
},
166 { "8bit", CE_8BIT
, Init7Bit
},
167 { "7bit", CE_7BIT
, Init7Bit
},
168 { "binary", CE_BINARY
, Init7Bit
},
169 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
170 { NULL
, CE_UNKNOWN
, NULL
},
174 * NOTE WELL: si_key MUST NOT have value of NOTOK
176 * si_key is 1 if access method is anonymous.
178 struct str2init str2methods
[] = {
179 { "afs", 1, InitFile
},
180 { "anon-ftp", 1, InitFTP
},
181 { "ftp", 0, InitFTP
},
182 { "local-file", 0, InitFile
},
183 { "mail-server", 0, InitMail
},
184 { "url", 0, InitURL
},
190 * Main entry point for parsing a MIME message or file.
191 * It returns the Content structure for the top level
192 * entity in the file.
196 parse_mime (char *file
)
205 bogus_mp_content
= 0;
208 * Check if file is actually standard input
210 if ((is_stdin
= !(strcmp (file
, "-")))) {
211 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
213 advise("mhparse", "unable to create temporary file in %s",
217 file
= mh_xstrdup(tfile
);
219 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
220 if (fwrite(buffer
, 1, n
, fp
) != n
) {
221 (void) m_unlink (file
);
222 advise (file
, "error copying to temporary file");
228 if (ferror (stdin
)) {
229 (void) m_unlink (file
);
230 advise ("stdin", "error reading");
234 (void) m_unlink (file
);
235 advise (file
, "error writing");
238 fseek (fp
, 0L, SEEK_SET
);
239 } else if (stat (file
, &statbuf
) == NOTOK
) {
240 advise (file
, "unable to stat");
242 } else if (S_ISDIR(statbuf
.st_mode
)) {
243 /* Don't try to parse a directory. */
244 inform("%s is a directory", file
);
246 } else if ((fp
= fopen (file
, "r")) == NULL
) {
247 advise (file
, "unable to read");
251 if (!(ct
= get_content (fp
, file
, 1))) {
253 (void) m_unlink (file
);
254 inform("unable to decode %s", file
);
259 ct
->c_unlink
= 1; /* temp file to remove */
263 if (ct
->c_end
== 0L) {
264 fseek (fp
, 0L, SEEK_END
);
265 ct
->c_end
= ftell (fp
);
268 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
280 * Main routine for reading/parsing the headers
281 * of a message content.
283 * toplevel = 1 # we are at the top level of the message
284 * toplevel = 0 # we are inside message type or multipart type
285 * # other than multipart/digest
286 * toplevel = -1 # we are inside multipart/digest
287 * NB: on failure we will fclose(in)!
291 get_content (FILE *in
, char *file
, int toplevel
)
294 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
298 m_getfld_state_t gstate
= 0;
300 /* allocate the content structure */
303 ct
->c_file
= add (file
, NULL
);
304 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
307 * Parse the header fields for this
308 * content into a linked list.
310 m_getfld_track_filepos (&gstate
, in
);
311 for (compnum
= 1;;) {
312 int bufsz
= sizeof buf
;
313 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
318 /* get copies of the buffers */
319 np
= mh_xstrdup(name
);
320 vp
= mh_xstrdup(buf
);
322 /* if necessary, get rest of field */
323 while (state
== FLDPLUS
) {
325 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
326 vp
= add (buf
, vp
); /* add to previous value */
329 /* Now add the header data to the list */
330 add_header (ct
, np
, vp
);
332 /* continue, to see if this isn't the last header field */
333 ct
->c_begin
= ftell (in
) + 1;
337 if (name
[0] == ':') {
338 /* Special case: no blank line between header and body. The
339 file position indicator is on the newline at the end of the
340 line, but it needs to be one prior to the beginning of the
341 line. So subtract the length of the line, bufsz, plus 1. */
342 ct
->c_begin
= ftell (in
) - (bufsz
+ 1);
344 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
349 ct
->c_begin
= ftell (in
);
354 adios (NULL
, "message format error in component #%d", compnum
);
357 adios (NULL
, "getfld() returned %d", state
);
360 /* break out of the loop */
363 m_getfld_state_destroy (&gstate
);
366 * Read the content headers. We will parse the
367 * MIME related header fields into their various
368 * structures and set internal flags related to
369 * content type/subtype, etc.
372 hp
= ct
->c_first_hf
; /* start at first header field */
374 /* Get MIME-Version field */
375 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
380 vrsn
= add (hp
->value
, NULL
);
382 /* Now, cleanup this field */
385 while (isspace ((unsigned char) *cp
))
387 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
389 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
390 if (!isspace ((unsigned char) *dp
))
394 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
397 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
400 for (dp
= cp
; istoken (*dp
); dp
++)
404 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
407 inform("message %s has unknown value for %s: field (%s), continuing...",
408 ct
->c_file
, VRSN_FIELD
, cp
);
413 if (! suppress_multiple_mime_version_warning
)
414 inform("message %s has multiple %s: fields",
415 ct
->c_file
, VRSN_FIELD
);
419 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
420 /* Get Content-Type field */
421 struct str2init
*s2i
;
422 CI ci
= &ct
->c_ctinfo
;
424 /* Check if we've already seen a Content-Type header */
426 inform("message %s has multiple %s: fields",
427 ct
->c_file
, TYPE_FIELD
);
431 /* Parse the Content-Type field */
432 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
436 * Set the Init function and the internal
437 * flag for this content type.
439 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
440 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
442 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
444 ct
->c_type
= s2i
->si_val
;
445 ct
->c_ctinitfnx
= s2i
->si_init
;
447 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
448 /* Get Content-Transfer-Encoding field */
450 struct str2init
*s2i
;
453 * Check if we've already seen the
454 * Content-Transfer-Encoding field
457 inform("message %s has multiple %s: fields",
458 ct
->c_file
, ENCODING_FIELD
);
462 /* get copy of this field */
463 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
465 while (isspace ((unsigned char) *cp
))
467 for (dp
= cp
; istoken (*dp
); dp
++)
473 * Find the internal flag and Init function
474 * for this transfer encoding.
476 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
477 if (!strcasecmp (cp
, s2i
->si_key
))
479 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
482 ct
->c_encoding
= s2i
->si_val
;
484 /* Call the Init function for this encoding */
485 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
488 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
489 /* Get Content-MD5 field */
495 if (ct
->c_digested
) {
496 inform("message %s has multiple %s: fields",
497 ct
->c_file
, MD5_FIELD
);
501 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
503 while (isspace ((unsigned char) *cp
))
505 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
507 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
508 if (!isspace ((unsigned char) *dp
))
512 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
515 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
520 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
528 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
529 /* Get Content-ID field */
530 ct
->c_id
= add (hp
->value
, ct
->c_id
);
532 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
533 /* Get Content-Description field */
534 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
536 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
537 /* Get Content-Disposition field */
538 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
543 hp
= hp
->next
; /* next header field */
547 * Check if we saw a Content-Type field.
548 * If not, then assign a default value for
549 * it, and the Init function.
553 * If we are inside a multipart/digest message,
554 * so default type is message/rfc822
557 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
559 ct
->c_type
= CT_MESSAGE
;
560 ct
->c_ctinitfnx
= InitMessage
;
563 * Else default type is text/plain
565 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
567 ct
->c_type
= CT_TEXT
;
568 ct
->c_ctinitfnx
= InitText
;
572 /* Use default Transfer-Encoding, if necessary */
574 ct
->c_encoding
= CE_7BIT
;
587 * small routine to add header field to list
591 add_header (CT ct
, char *name
, char *value
)
595 /* allocate header field structure */
598 /* link data into header structure */
603 /* link header structure into the list */
604 if (ct
->c_first_hf
== NULL
) {
605 ct
->c_first_hf
= hp
; /* this is the first */
608 ct
->c_last_hf
->next
= hp
; /* add it to the end */
617 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
618 * directives. Fills in the information of the CTinfo structure.
621 get_ctinfo (char *cp
, CT ct
, int magic
)
630 /* store copy of Content-Type line */
631 cp
= ct
->c_ctline
= add (cp
, NULL
);
633 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
636 /* change newlines to spaces */
637 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
640 /* trim trailing spaces */
641 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
642 if (!isspace ((unsigned char) *dp
))
647 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
649 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
650 &ci
->ci_comment
) == NOTOK
)
653 for (dp
= cp
; istoken (*dp
); dp
++)
657 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
662 inform("invalid %s: field in message %s (empty type)",
663 TYPE_FIELD
, ct
->c_file
);
666 to_lower(ci
->ci_type
);
668 while (isspace ((unsigned char) *cp
))
671 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
672 &ci
->ci_comment
) == NOTOK
)
677 ci
->ci_subtype
= mh_xstrdup("");
682 while (isspace ((unsigned char) *cp
))
685 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
686 &ci
->ci_comment
) == NOTOK
)
689 for (dp
= cp
; istoken (*dp
); dp
++)
693 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
697 if (!*ci
->ci_subtype
) {
698 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
699 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
702 to_lower(ci
->ci_subtype
);
705 while (isspace ((unsigned char) *cp
))
708 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
709 &ci
->ci_comment
) == NOTOK
)
712 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
713 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
714 &ci
->ci_comment
)) != OK
) {
715 return status
== NOTOK
? NOTOK
: OK
;
719 * Get any <Content-Id> given in buffer
721 if (magic
&& *cp
== '<') {
724 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
725 inform("invalid ID in message %s", ct
->c_file
);
731 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
737 while (isspace ((unsigned char) *cp
))
742 * Get any [Content-Description] given in buffer.
744 if (magic
&& *cp
== '[') {
746 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
750 inform("invalid description in message %s", ct
->c_file
);
758 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
764 while (isspace ((unsigned char) *cp
))
769 * Get any {Content-Disposition} given in buffer.
771 if (magic
&& *cp
== '{') {
773 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
777 inform("invalid disposition in message %s", ct
->c_file
);
785 if (get_dispo(cp
, ct
, 1) != OK
)
791 while (isspace ((unsigned char) *cp
))
796 * Get any extension directives (right now just the content transfer
797 * encoding, but maybe others) that we care about.
800 if (magic
&& *cp
== '*') {
802 * See if it's a CTE we match on
807 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
811 inform("invalid null transfer encoding specification");
818 ct
->c_reqencoding
= CE_UNKNOWN
;
820 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
821 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
822 ct
->c_reqencoding
= kv
->kv_value
;
827 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
828 inform("invalid CTE specification: \"%s\"", dp
);
832 while (isspace ((unsigned char) *cp
))
837 * Check if anything is left over
841 ci
->ci_magic
= mh_xstrdup(cp
);
843 /* If there is a Content-Disposition header and it doesn't
844 have a *filename=, extract it from the magic contents.
845 The r1bindex call skips any leading directory
847 if (ct
->c_dispo_type
&&
848 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
849 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
850 r1bindex(ci
->ci_magic
, '/'), 0);
854 inform("extraneous information in message %s's %s: field\n%*s(%s)",
855 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
863 * Parse out a Content-Disposition header. A lot of this is cribbed from
867 get_dispo (char *cp
, CT ct
, int buildflag
)
869 char *dp
, *dispoheader
;
874 * Save the whole copy of the Content-Disposition header, unless we're
875 * processing a mhbuild directive. A NULL c_dispo will be a flag to
876 * mhbuild that the disposition header needs to be generated at that
880 dispoheader
= cp
= add(cp
, NULL
);
882 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
885 /* change newlines to spaces */
886 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
889 /* trim trailing spaces */
890 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
891 if (!isspace ((unsigned char) *dp
))
896 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
898 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
904 for (dp
= cp
; istoken (*dp
); dp
++)
908 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
912 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
915 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
916 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
918 if (status
== NOTOK
) {
923 inform("extraneous information in message %s's %s: field\n%*s(%s)",
924 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
930 ct
->c_dispo
= dispoheader
;
937 get_comment (const char *filename
, const char *fieldname
, char **ap
,
942 char c
, buffer
[BUFSIZ
], *dp
;
952 inform("invalid comment in message %s's %s: field",
953 filename
, fieldname
);
958 if ((c
= *cp
++) == '\0')
981 if ((dp
= *commentp
)) {
982 *commentp
= concat (dp
, " ", buffer
, NULL
);
985 *commentp
= mh_xstrdup(buffer
);
989 while (isspace ((unsigned char) *cp
))
1000 * Handles content types audio, image, and video.
1001 * There's not much to do right here.
1009 return OK
; /* not much to do here */
1020 char buffer
[BUFSIZ
];
1025 CI ci
= &ct
->c_ctinfo
;
1027 /* check for missing subtype */
1028 if (!*ci
->ci_subtype
)
1029 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1032 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1034 /* allocate text character set structure */
1036 ct
->c_ctparams
= (void *) t
;
1038 /* scan for charset parameter */
1039 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1040 if (!strcasecmp (pm
->pm_name
, "charset"))
1043 /* check if content specified a character set */
1045 chset
= pm
->pm_value
;
1046 t
->tx_charset
= CHARSET_SPECIFIED
;
1048 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1052 * If we can not handle character set natively,
1053 * then check profile for string to modify the
1054 * terminal or display method.
1056 * termproc is for mhshow, though mhlist -debug prints it, too.
1058 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1059 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1060 if ((cp
= context_find (buffer
)))
1061 ct
->c_termproc
= mh_xstrdup(cp
);
1073 InitMultiPart (CT ct
)
1083 struct multipart
*m
;
1084 struct part
*part
, **next
;
1085 CI ci
= &ct
->c_ctinfo
;
1090 * The encoding for multipart messages must be either
1091 * 7bit, 8bit, or binary (per RFC 2045).
1093 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1094 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1095 /* Copy the Content-Transfer-Encoding header field body so we can
1096 remove any trailing whitespace and leading blanks from it. */
1097 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1099 bp
= cte
+ strlen (cte
) - 1;
1100 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1101 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1103 inform("\"%s/%s\" type in message %s must be encoded in\n"
1104 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1105 "mhfixmsg -fixcte can fix it, or\n"
1106 "manually edit the file and change the \"%s\"\n"
1107 "Content-Transfer-Encoding to one of those. For now, continuing...",
1108 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1115 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1118 * Check for "boundary" parameter, which is
1119 * required for multipart messages.
1122 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1123 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1129 /* complain if boundary parameter is missing */
1131 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1132 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1136 /* allocate primary structure for multipart info */
1138 ct
->c_ctparams
= (void *) m
;
1140 /* check if boundary parameter contains only whitespace characters */
1141 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1144 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1145 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1149 /* remove trailing whitespace from boundary parameter */
1150 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1151 if (!isspace ((unsigned char) *dp
))
1155 /* record boundary separators */
1156 m
->mp_start
= concat (bp
, "\n", NULL
);
1157 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1159 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1160 advise (ct
->c_file
, "unable to open for reading");
1164 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1166 next
= &m
->mp_parts
;
1170 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1175 if (bufp
[0] != '-' || bufp
[1] != '-')
1178 if (strcmp (bufp
+ 2, m
->mp_start
))
1183 next
= &part
->mp_next
;
1185 if (!(p
= get_content (fp
, ct
->c_file
,
1186 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1194 fseek (fp
, pos
, SEEK_SET
);
1197 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1201 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1202 if (p
->c_end
< p
->c_begin
)
1203 p
->c_begin
= p
->c_end
;
1208 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1213 if (! suppress_bogus_mp_content_warning
) {
1214 inform("bogus multipart content in message %s", ct
->c_file
);
1216 bogus_mp_content
= 1;
1218 if (!inout
&& part
) {
1220 p
->c_end
= ct
->c_end
;
1222 if (p
->c_begin
>= p
->c_end
) {
1223 for (next
= &m
->mp_parts
; *next
!= part
;
1224 next
= &((*next
)->mp_next
))
1233 /* reverse the order of the parts for multipart/alternative */
1234 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1240 * label all subparts with part number, and
1241 * then initialize the content of the subpart.
1246 char partnam
[BUFSIZ
];
1249 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1250 pp
= partnam
+ strlen (partnam
);
1255 for (part
= m
->mp_parts
, partnum
= 1; part
;
1256 part
= part
->mp_next
, partnum
++) {
1259 sprintf (pp
, "%d", partnum
);
1260 p
->c_partno
= mh_xstrdup(partnam
);
1262 /* initialize the content of the subparts */
1263 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1272 get_leftover_mp_content (ct
, 1);
1273 get_leftover_mp_content (ct
, 0);
1283 * reverse the order of the parts of a multipart/alternative,
1284 * presumably to put the "most favored" alternative first, for
1285 * ease of choosing/displaying it later on. from a mail message on
1286 * nmh-workers, from kenh:
1287 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1288 * see code in mhn that did the same thing... According to the RCS
1289 * logs, that code was around from the initial checkin of mhn.c by
1290 * John Romine in 1992, which is as far back as we have."
1293 reverse_parts (CT ct
)
1295 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1299 /* Reverse the order of its parts by walking the mp_parts list
1300 and pushing each node to the front. */
1301 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1302 next
= part
->mp_next
;
1303 part
->mp_next
= m
->mp_parts
;
1309 move_preferred_part (CT ct
, char *type
, char *subtype
)
1311 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1312 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1316 /* move the matching part(s) to the head of the list: walk the
1317 * list of parts, move matching parts to a new list (maintaining
1318 * their order), and finally, concatenate the old list onto the
1325 head
->mp_next
= m
->mp_parts
;
1326 nhead
->mp_next
= NULL
;
1330 part
= head
->mp_next
;
1331 while (part
!= NULL
) {
1332 ci
= &part
->mp_part
->c_ctinfo
;
1333 if (!strcasecmp(ci
->ci_type
, type
) &&
1334 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1335 prev
->mp_next
= part
->mp_next
;
1336 part
->mp_next
= NULL
;
1337 ntail
->mp_next
= part
;
1339 part
= prev
->mp_next
;
1342 part
= prev
->mp_next
;
1345 ntail
->mp_next
= head
->mp_next
;
1346 m
->mp_parts
= nhead
->mp_next
;
1351 * move parts that match the user's preferences (-prefer) to the head
1352 * of the line. process preferences in reverse so first one given
1353 * ends up first in line
1359 for (i
= npreferred
-1; i
>= 0; i
--)
1360 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1365 /* parse_mime() arranges alternates in reverse (priority) order. This
1366 function can be used to reverse them back. This will put, for
1367 example, a text/plain part before a text/html part in a
1368 multipart/alternative part, for example, where it belongs. */
1370 reverse_alternative_parts (CT ct
) {
1371 if (ct
->c_type
== CT_MULTIPART
) {
1372 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1375 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1379 /* And call recursively on each part of a multipart. */
1380 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1381 reverse_alternative_parts (part
->mp_part
);
1394 CI ci
= &ct
->c_ctinfo
;
1396 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1397 inform("\"%s/%s\" type in message %s should be encoded in "
1398 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1403 /* check for missing subtype */
1404 if (!*ci
->ci_subtype
)
1405 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1408 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1410 switch (ct
->c_subtype
) {
1411 case MESSAGE_RFC822
:
1414 case MESSAGE_PARTIAL
:
1420 ct
->c_ctparams
= (void *) p
;
1422 /* scan for parameters "id", "number", and "total" */
1423 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1424 if (!strcasecmp (pm
->pm_name
, "id")) {
1425 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1428 if (!strcasecmp (pm
->pm_name
, "number")) {
1429 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1430 || p
->pm_partno
< 1) {
1432 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1433 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1434 ct
->c_file
, TYPE_FIELD
);
1439 if (!strcasecmp (pm
->pm_name
, "total")) {
1440 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1449 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1450 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1451 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1457 case MESSAGE_EXTERNAL
:
1465 ct
->c_ctparams
= (void *) e
;
1468 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1469 advise (ct
->c_file
, "unable to open for reading");
1473 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1475 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1483 p
->c_ceopenfnx
= NULL
;
1484 if ((exresult
= params_external (ct
, 0)) != NOTOK
1485 && p
->c_ceopenfnx
== openMail
) {
1489 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1491 content_error (NULL
, ct
,
1492 "empty body for access-type=mail-server");
1496 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1497 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1499 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1501 adios ("failed", "fread");
1504 adios (NULL
, "unexpected EOF from fread");
1507 bp
+= cc
, size
-= cc
;
1514 p
->c_end
= p
->c_begin
;
1519 if (exresult
== NOTOK
)
1521 if (e
->eb_flags
== NOTOK
)
1524 switch (p
->c_type
) {
1529 if (p
->c_subtype
!= MESSAGE_RFC822
)
1533 e
->eb_partno
= ct
->c_partno
;
1535 (*p
->c_ctinitfnx
) (p
);
1550 params_external (CT ct
, int composing
)
1553 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1554 CI ci
= &ct
->c_ctinfo
;
1556 ct
->c_ceopenfnx
= NULL
;
1557 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1558 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1559 struct str2init
*s2i
;
1560 CT p
= e
->eb_content
;
1562 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1563 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1566 e
->eb_access
= pm
->pm_value
;
1567 e
->eb_flags
= NOTOK
;
1568 p
->c_encoding
= CE_EXTERNAL
;
1571 e
->eb_access
= s2i
->si_key
;
1572 e
->eb_flags
= s2i
->si_val
;
1573 p
->c_encoding
= CE_EXTERNAL
;
1575 /* Call the Init function for this external type */
1576 if ((*s2i
->si_init
)(p
) == NOTOK
)
1580 if (!strcasecmp (pm
->pm_name
, "name")) {
1581 e
->eb_name
= pm
->pm_value
;
1584 if (!strcasecmp (pm
->pm_name
, "permission")) {
1585 e
->eb_permission
= pm
->pm_value
;
1588 if (!strcasecmp (pm
->pm_name
, "site")) {
1589 e
->eb_site
= pm
->pm_value
;
1592 if (!strcasecmp (pm
->pm_name
, "directory")) {
1593 e
->eb_dir
= pm
->pm_value
;
1596 if (!strcasecmp (pm
->pm_name
, "mode")) {
1597 e
->eb_mode
= pm
->pm_value
;
1600 if (!strcasecmp (pm
->pm_name
, "size")) {
1601 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1604 if (!strcasecmp (pm
->pm_name
, "server")) {
1605 e
->eb_server
= pm
->pm_value
;
1608 if (!strcasecmp (pm
->pm_name
, "subject")) {
1609 e
->eb_subject
= pm
->pm_value
;
1612 if (!strcasecmp (pm
->pm_name
, "url")) {
1614 * According to RFC 2017, we have to remove all whitespace from
1618 char *u
, *p
= pm
->pm_value
;
1619 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1621 for (; *p
!= '\0'; p
++) {
1622 if (! isspace((unsigned char) *p
))
1629 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1630 e
->eb_body
= getcpy (pm
->pm_value
);
1635 if (!e
->eb_access
) {
1636 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1637 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1650 InitApplication (CT ct
)
1652 CI ci
= &ct
->c_ctinfo
;
1655 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1662 * TRANSFER ENCODINGS
1666 init_encoding (CT ct
, OpenCEFunc openfnx
)
1668 ct
->c_ceopenfnx
= openfnx
;
1669 ct
->c_ceclosefnx
= close_encoding
;
1670 ct
->c_cesizefnx
= size_encoding
;
1677 close_encoding (CT ct
)
1679 CE ce
= &ct
->c_cefile
;
1688 static unsigned long
1689 size_encoding (CT ct
)
1694 CE ce
= &ct
->c_cefile
;
1697 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1698 return (long) st
.st_size
;
1701 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1702 return (long) st
.st_size
;
1706 if (ct
->c_encoding
== CE_EXTERNAL
)
1707 return (ct
->c_end
- ct
->c_begin
);
1710 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1711 return (ct
->c_end
- ct
->c_begin
);
1713 if (fstat (fd
, &st
) != NOTOK
)
1714 size
= (long) st
.st_size
;
1718 (*ct
->c_ceclosefnx
) (ct
);
1730 return init_encoding (ct
, openBase64
);
1735 openBase64 (CT ct
, char **file
)
1738 int fd
, own_ct_fp
= 0;
1739 char *cp
, *buffer
= NULL
;
1740 /* sbeck -- handle suffixes */
1742 CE ce
= &ct
->c_cefile
;
1743 unsigned char *decoded
;
1745 unsigned char digest
[16];
1748 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1753 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1754 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1760 if (*file
== NULL
) {
1763 ce
->ce_file
= mh_xstrdup(*file
);
1767 /* sbeck@cise.ufl.edu -- handle suffixes */
1769 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1770 if (ce
->ce_unlink
) {
1771 /* Create temporary file with filename extension. */
1772 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1773 adios(NULL
, "unable to create temporary file in %s",
1777 ce
->ce_file
= add (cp
, ce
->ce_file
);
1779 } else if (*file
== NULL
) {
1781 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1782 adios(NULL
, "unable to create temporary file in %s",
1785 ce
->ce_file
= mh_xstrdup(tempfile
);
1788 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1789 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1793 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1794 adios (NULL
, "internal error(1)");
1796 buffer
= mh_xmalloc (len
+ 1);
1799 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1800 content_error (ct
->c_file
, ct
, "unable to open for reading");
1806 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1809 switch (cc
= read (fd
, cp
, len
)) {
1811 content_error (ct
->c_file
, ct
, "error reading from");
1815 content_error (NULL
, ct
, "premature eof");
1826 /* decodeBase64() requires null-terminated input. */
1829 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1830 ct
->c_digested
? digest
: NULL
) == OK
) {
1832 unsigned char *decoded_p
= decoded
;
1833 for (i
= 0; i
< decoded_len
; ++i
) {
1834 putc (*decoded_p
++, ce
->ce_fp
);
1837 if (ferror (ce
->ce_fp
)) {
1838 content_error (ce
->ce_file
, ct
, "error writing to");
1842 if (ct
->c_digested
) {
1843 if (memcmp(digest
, ct
->c_digest
,
1844 sizeof(digest
) / sizeof(digest
[0]))) {
1845 content_error (NULL
, ct
,
1846 "content integrity suspect (digest mismatch) -- continuing");
1849 fprintf (stderr
, "content integrity confirmed\n");
1857 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1859 if (fflush (ce
->ce_fp
)) {
1860 content_error (ce
->ce_file
, ct
, "error writing to");
1864 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1867 *file
= ce
->ce_file
;
1873 return fileno (ce
->ce_fp
);
1880 free_encoding (ct
, 0);
1890 static char hex2nib
[0x80] = {
1891 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1892 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1893 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1894 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1895 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1896 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1897 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1898 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1899 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1900 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1901 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1903 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1913 return init_encoding (ct
, openQuoted
);
1918 openQuoted (CT ct
, char **file
)
1920 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1926 CE ce
= &ct
->c_cefile
;
1927 /* sbeck -- handle suffixes */
1932 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1937 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1938 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1944 if (*file
== NULL
) {
1947 ce
->ce_file
= mh_xstrdup(*file
);
1951 /* sbeck@cise.ufl.edu -- handle suffixes */
1953 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1954 if (ce
->ce_unlink
) {
1955 /* Create temporary file with filename extension. */
1956 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1957 adios(NULL
, "unable to create temporary file in %s",
1961 ce
->ce_file
= add (cp
, ce
->ce_file
);
1963 } else if (*file
== NULL
) {
1965 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1966 adios(NULL
, "unable to create temporary file in %s",
1969 ce
->ce_file
= mh_xstrdup(tempfile
);
1972 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1973 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1977 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1978 adios (NULL
, "internal error(2)");
1981 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1982 content_error (ct
->c_file
, ct
, "unable to open for reading");
1988 if ((digested
= ct
->c_digested
))
1989 MD5Init (&mdContext
);
1996 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1998 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
1999 content_error (NULL
, ct
, "premature eof");
2003 if ((cc
= gotlen
) > len
)
2007 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2008 if (!isspace ((unsigned char) *ep
))
2013 for (; cp
< ep
; cp
++) {
2015 /* in an escape sequence */
2017 /* at byte 1 of an escape sequence */
2018 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2019 /* next is byte 2 */
2022 /* at byte 2 of an escape sequence */
2024 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2025 putc (mask
, ce
->ce_fp
);
2027 MD5Update (&mdContext
, &mask
, 1);
2028 if (ferror (ce
->ce_fp
)) {
2029 content_error (ce
->ce_file
, ct
, "error writing to");
2032 /* finished escape sequence; next may be literal or a new
2033 * escape sequence */
2036 /* on to next byte */
2040 /* not in an escape sequence */
2042 /* starting an escape sequence, or invalid '='? */
2043 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2044 /* "=\n" soft line break, eat the \n */
2048 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2049 /* We don't have 2 bytes left, so this is an invalid
2050 * escape sequence; just show the raw bytes (below). */
2051 } else if (isxdigit ((unsigned char) cp
[1]) &&
2052 isxdigit ((unsigned char) cp
[2])) {
2053 /* Next 2 bytes are hex digits, making this a valid escape
2054 * sequence; let's decode it (above). */
2058 /* One or both of the next 2 is out of range, making this
2059 * an invalid escape sequence; just show the raw bytes
2063 /* Just show the raw byte. */
2064 putc (*cp
, ce
->ce_fp
);
2067 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2069 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2072 if (ferror (ce
->ce_fp
)) {
2073 content_error (ce
->ce_file
, ct
, "error writing to");
2079 content_error (NULL
, ct
,
2080 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2084 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2086 if (fflush (ce
->ce_fp
)) {
2087 content_error (ce
->ce_file
, ct
, "error writing to");
2092 unsigned char digest
[16];
2094 MD5Final (digest
, &mdContext
);
2095 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2096 sizeof(digest
) / sizeof(digest
[0])))
2097 content_error (NULL
, ct
,
2098 "content integrity suspect (digest mismatch) -- continuing");
2101 fprintf (stderr
, "content integrity confirmed\n");
2104 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2107 *file
= ce
->ce_file
;
2113 return fileno (ce
->ce_fp
);
2116 free_encoding (ct
, 0);
2133 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2136 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2142 open7Bit (CT ct
, char **file
)
2144 int cc
, fd
, len
, own_ct_fp
= 0;
2145 char buffer
[BUFSIZ
];
2146 /* sbeck -- handle suffixes */
2149 CE ce
= &ct
->c_cefile
;
2152 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2157 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2158 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2164 if (*file
== NULL
) {
2167 ce
->ce_file
= mh_xstrdup(*file
);
2171 /* sbeck@cise.ufl.edu -- handle suffixes */
2173 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2174 if (ce
->ce_unlink
) {
2175 /* Create temporary file with filename extension. */
2176 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2177 adios(NULL
, "unable to create temporary file in %s",
2181 ce
->ce_file
= add (cp
, ce
->ce_file
);
2183 } else if (*file
== NULL
) {
2185 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2186 adios(NULL
, "unable to create temporary file in %s",
2189 ce
->ce_file
= mh_xstrdup(tempfile
);
2192 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2193 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2197 if (ct
->c_type
== CT_MULTIPART
) {
2198 CI ci
= &ct
->c_ctinfo
;
2202 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2203 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2204 + 1 + strlen (ci
->ci_subtype
);
2205 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2208 fputs (buffer
, ce
->ce_fp
);
2212 if (ci
->ci_comment
) {
2213 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2214 fputs ("\n\t", ce
->ce_fp
);
2218 putc (' ', ce
->ce_fp
);
2221 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2224 fprintf (ce
->ce_fp
, "\n");
2226 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2228 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2230 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2231 fprintf (ce
->ce_fp
, "\n");
2234 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2235 adios (NULL
, "internal error(3)");
2238 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2239 content_error (ct
->c_file
, ct
, "unable to open for reading");
2245 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2247 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2249 content_error (ct
->c_file
, ct
, "error reading from");
2253 content_error (NULL
, ct
, "premature eof");
2261 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2262 advise ("open7Bit", "fwrite");
2264 if (ferror (ce
->ce_fp
)) {
2265 content_error (ce
->ce_file
, ct
, "error writing to");
2270 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2272 if (fflush (ce
->ce_fp
)) {
2273 content_error (ce
->ce_file
, ct
, "error writing to");
2277 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2280 *file
= ce
->ce_file
;
2285 return fileno (ce
->ce_fp
);
2288 free_encoding (ct
, 0);
2302 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2304 char cachefile
[BUFSIZ
];
2307 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2312 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2313 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2319 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2320 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2321 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2322 ce
->ce_file
= mh_xstrdup(cachefile
);
2326 admonish (cachefile
, "unable to fopen for reading");
2329 *fd
= fileno (ce
->ce_fp
);
2333 *file
= ce
->ce_file
;
2334 *fd
= fileno (ce
->ce_fp
);
2345 return init_encoding (ct
, openFile
);
2350 openFile (CT ct
, char **file
)
2353 char cachefile
[BUFSIZ
];
2354 struct exbody
*e
= ct
->c_ctexbody
;
2355 CE ce
= &ct
->c_cefile
;
2357 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2369 content_error (NULL
, ct
, "missing name parameter");
2373 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2376 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2377 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2381 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2382 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2383 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2387 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2388 if ((fp
= fopen (cachefile
, "w"))) {
2390 char buffer
[BUFSIZ
];
2391 FILE *gp
= ce
->ce_fp
;
2393 fseek (gp
, 0L, SEEK_SET
);
2395 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2397 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2398 advise ("openFile", "fwrite");
2403 admonish (ce
->ce_file
, "error reading");
2404 (void) m_unlink (cachefile
);
2408 admonish (cachefile
, "error writing");
2409 (void) m_unlink (cachefile
);
2416 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2417 *file
= ce
->ce_file
;
2418 return fileno (ce
->ce_fp
);
2428 return init_encoding (ct
, openFTP
);
2433 openFTP (CT ct
, char **file
)
2435 int cachetype
, caching
, fd
;
2437 char *bp
, *ftp
, *user
, *pass
;
2438 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2440 CE ce
= &ct
->c_cefile
;
2441 static char *username
= NULL
;
2442 static char *password
= NULL
;
2446 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2452 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2463 if (!e
->eb_name
|| !e
->eb_site
) {
2464 content_error (NULL
, ct
, "missing %s parameter",
2465 e
->eb_name
? "site": "name");
2469 /* Get the buffer ready to go */
2471 buflen
= sizeof(buffer
);
2474 * Construct the query message for user
2476 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2482 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2488 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2489 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2494 if (e
->eb_size
> 0) {
2495 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2500 snprintf (bp
, buflen
, "? ");
2503 * Now, check the answer
2505 if (!read_yes_or_no_if_tty (buffer
))
2510 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2514 ruserpass (e
->eb_site
, &username
, &password
, 0);
2519 ce
->ce_unlink
= (*file
== NULL
);
2521 cachefile
[0] = '\0';
2522 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2523 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2524 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2525 if (*file
== NULL
) {
2532 ce
->ce_file
= mh_xstrdup(*file
);
2534 ce
->ce_file
= mh_xstrdup(cachefile
);
2537 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2538 adios(NULL
, "unable to create temporary file in %s",
2541 ce
->ce_file
= mh_xstrdup(tempfile
);
2544 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2545 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2550 int child_id
, i
, vecp
;
2554 vec
[vecp
++] = r1bindex (ftp
, '/');
2555 vec
[vecp
++] = e
->eb_site
;
2558 vec
[vecp
++] = e
->eb_dir
;
2559 vec
[vecp
++] = e
->eb_name
;
2560 vec
[vecp
++] = ce
->ce_file
,
2561 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2562 ? "ascii" : "binary";
2567 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2571 adios ("fork", "unable to");
2575 close (fileno (ce
->ce_fp
));
2577 fprintf (stderr
, "unable to exec ");
2583 if (pidXwait (child_id
, NULL
)) {
2584 username
= password
= NULL
;
2594 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2599 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2600 if ((fp
= fopen (cachefile
, "w"))) {
2602 FILE *gp
= ce
->ce_fp
;
2604 fseek (gp
, 0L, SEEK_SET
);
2606 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2608 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2609 advise ("openFTP", "fwrite");
2614 admonish (ce
->ce_file
, "error reading");
2615 (void) m_unlink (cachefile
);
2619 admonish (cachefile
, "error writing");
2620 (void) m_unlink (cachefile
);
2628 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2629 *file
= ce
->ce_file
;
2630 return fileno (ce
->ce_fp
);
2641 return init_encoding (ct
, openMail
);
2646 openMail (CT ct
, char **file
)
2648 int child_id
, fd
, i
, vecp
;
2650 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2651 struct exbody
*e
= ct
->c_ctexbody
;
2652 CE ce
= &ct
->c_cefile
;
2654 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2665 if (!e
->eb_server
) {
2666 content_error (NULL
, ct
, "missing server parameter");
2670 /* Get buffer ready to go */
2672 buflen
= sizeof(buffer
);
2674 /* Now, construct query message */
2675 snprintf (bp
, buflen
, "Retrieve content");
2681 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2687 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2689 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2691 /* Now, check answer */
2692 if (!read_yes_or_no_if_tty (buffer
))
2696 vec
[vecp
++] = r1bindex (mailproc
, '/');
2697 vec
[vecp
++] = e
->eb_server
;
2698 vec
[vecp
++] = "-subject";
2699 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2700 vec
[vecp
++] = "-body";
2701 vec
[vecp
++] = e
->eb_body
;
2704 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2708 advise ("fork", "unable to");
2712 execvp (mailproc
, vec
);
2713 fprintf (stderr
, "unable to exec ");
2719 if (pidXwait (child_id
, NULL
) == OK
)
2720 inform("request sent");
2724 if (*file
== NULL
) {
2726 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2727 adios(NULL
, "unable to create temporary file in %s",
2730 ce
->ce_file
= mh_xstrdup(tempfile
);
2733 ce
->ce_file
= mh_xstrdup(*file
);
2737 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2738 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2742 /* showproc is for mhshow and mhstore, though mhlist -debug
2743 * prints it, too. */
2744 mh_xfree(ct
->c_showproc
);
2745 ct
->c_showproc
= mh_xstrdup("true");
2747 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2748 *file
= ce
->ce_file
;
2749 return fileno (ce
->ce_fp
);
2760 return init_encoding (ct
, openURL
);
2765 openURL (CT ct
, char **file
)
2767 struct exbody
*e
= ct
->c_ctexbody
;
2768 CE ce
= &ct
->c_cefile
;
2769 char *urlprog
, *program
;
2770 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2771 int fd
, caching
, cachetype
;
2772 struct msgs_array args
= { 0, 0, NULL
};
2775 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2779 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2783 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2795 content_error(NULL
, ct
, "missing url parameter");
2799 ce
->ce_unlink
= (*file
== NULL
);
2801 cachefile
[0] = '\0';
2803 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2804 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2805 if (*file
== NULL
) {
2812 ce
->ce_file
= mh_xstrdup(*file
);
2814 ce
->ce_file
= mh_xstrdup(cachefile
);
2817 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2818 adios(NULL
, "unable to create temporary file in %s",
2821 ce
->ce_file
= mh_xstrdup(tempfile
);
2824 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2825 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2829 switch (child_id
= fork()) {
2831 adios ("fork", "unable to");
2835 argsplit_msgarg(&args
, urlprog
, &program
);
2836 app_msgarg(&args
, e
->eb_url
);
2837 app_msgarg(&args
, NULL
);
2838 dup2(fileno(ce
->ce_fp
), 1);
2839 close(fileno(ce
->ce_fp
));
2840 execvp(program
, args
.msgs
);
2841 fprintf(stderr
, "Unable to exec ");
2847 if (pidXwait(child_id
, NULL
)) {
2855 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2860 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2861 if ((fp
= fopen(cachefile
, "w"))) {
2863 FILE *gp
= ce
->ce_fp
;
2865 fseeko(gp
, 0, SEEK_SET
);
2867 while ((cc
= fread(buffer
, sizeof(*buffer
),
2868 sizeof(buffer
), gp
)) > 0)
2869 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2870 advise ("openURL", "fwrite");
2876 admonish(ce
->ce_file
, "error reading");
2877 (void) m_unlink (cachefile
);
2884 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2885 *file
= ce
->ce_file
;
2891 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2892 * has to be base64 decoded.
2895 readDigest (CT ct
, char *cp
)
2897 unsigned char *digest
;
2900 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2901 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2903 if (strlen ((char *) digest
) <= maxlen
) {
2904 memcpy (ct
->c_digest
, digest
, maxlen
);
2909 fprintf (stderr
, "MD5 digest=");
2910 for (i
= 0; i
< maxlen
; ++i
) {
2911 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2913 fprintf (stderr
, "\n");
2919 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2920 (int) strlen ((char *) digest
));
2930 /* Multipart parts might have content before the first subpart and/or
2931 after the last subpart that hasn't been stored anywhere else, so do
2934 get_leftover_mp_content (CT ct
, int before
/* or after */)
2936 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2938 int found_boundary
= 0;
2944 char *content
= NULL
;
2946 if (! m
) return NOTOK
;
2949 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2951 /* Isolate the beginning of this part to the beginning of the
2952 first subpart and save any content between them. */
2953 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2954 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2955 boundary
= concat ("--", m
->mp_start
, NULL
);
2957 struct part
*last_subpart
= NULL
;
2958 struct part
*subpart
;
2960 /* Go to the last subpart to get its end position. */
2961 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2962 last_subpart
= subpart
;
2965 if (last_subpart
== NULL
) return NOTOK
;
2967 /* Isolate the end of the last subpart to the end of this part
2968 and save any content between them. */
2969 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2970 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2971 boundary
= concat ("--", m
->mp_stop
, NULL
);
2974 /* Back up by 1 to pick up the newline. */
2975 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2977 /* Don't look beyond beginning of first subpart (before) or
2978 next part (after). */
2979 if (read
> max
) bufp
[read
-max
] = '\0';
2982 if (! strcmp (bufp
, boundary
)) {
2986 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2992 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2994 char *old_content
= content
;
2995 content
= concat (content
, bufp
, NULL
);
2999 ? concat ("\n", bufp
, NULL
)
3000 : concat (bufp
, NULL
);
3005 if (found_boundary
|| read
> max
) break;
3007 if (read
> max
) break;
3011 /* Skip the newline if that's all there is. */
3015 /* Remove trailing newline, except at EOF. */
3016 if ((before
|| ! feof (ct
->c_fp
)) &&
3017 (cp
= content
+ strlen (content
)) > content
&&
3022 if (strlen (content
) > 1) {
3024 m
->mp_content_before
= content
;
3026 m
->mp_content_after
= content
;
3041 ct_type_str (int type
) {
3043 case CT_APPLICATION
:
3044 return "application";
3060 return "unknown_type";
3066 ct_subtype_str (int type
, int subtype
) {
3068 case CT_APPLICATION
:
3070 case APPLICATION_OCTETS
:
3072 case APPLICATION_POSTSCRIPT
:
3073 return "postscript";
3075 return "unknown_app_subtype";
3079 case MESSAGE_RFC822
:
3081 case MESSAGE_PARTIAL
:
3083 case MESSAGE_EXTERNAL
:
3086 return "unknown_msg_subtype";
3092 case MULTI_ALTERNATE
:
3093 return "alternative";
3096 case MULTI_PARALLEL
:
3101 return "unknown_multipart_subtype";
3112 return "unknown_text_subtype";
3115 return "unknown_type";
3121 ct_str_type (const char *type
) {
3122 struct str2init
*s2i
;
3124 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3125 if (! strcasecmp (type
, s2i
->si_key
)) {
3129 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3138 ct_str_subtype (int type
, const char *subtype
) {
3142 case CT_APPLICATION
:
3143 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3144 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3148 return kv
->kv_value
;
3150 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3151 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3155 return kv
->kv_value
;
3157 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3158 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3162 return kv
->kv_value
;
3164 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3165 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3169 return kv
->kv_value
;
3176 /* Find the content type and InitFunc for the CT. */
3177 const struct str2init
*
3178 get_ct_init (int type
) {
3179 const struct str2init
*sp
;
3181 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3182 if (type
== sp
->si_val
) {
3191 ce_str (int encoding
) {
3196 return "quoted-printable";
3212 /* Find the content type and InitFunc for the content encoding method. */
3213 const struct str2init
*
3214 get_ce_method (const char *method
) {
3215 struct str2init
*sp
;
3217 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3218 if (! strcasecmp (method
, sp
->si_key
)) {
3227 * Parse a series of MIME attributes (or parameters) given a header as
3230 * Arguments include:
3232 * filename - Name of input file (for error messages)
3233 * fieldname - Name of field being processed
3234 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3235 * Updated to point to end of attributes when finished.
3236 * param_head - Pointer to head of parameter list
3237 * param_tail - Pointer to tail of parameter list
3238 * commentp - Pointer to header comment pointer (may be NULL)
3240 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3241 * DONE to indicate a benign error (minor parsing error, but the program
3246 parse_header_attrs (const char *filename
, const char *fieldname
,
3247 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3250 char *cp
= *header_attrp
;
3256 struct sectlist
*next
;
3262 struct sectlist
*sechead
;
3263 struct parmlist
*next
;
3264 } *pp
, *pp2
, *phead
= NULL
;
3266 while (*cp
== ';') {
3267 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3268 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3271 while (isspace ((unsigned char) *cp
))
3275 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3280 if (! suppress_extraneous_trailing_semicolon_warning
) {
3281 inform("extraneous trailing ';' in message %s's %s: "
3282 "parameter list", filename
, fieldname
);
3287 /* down case the attribute name */
3288 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3289 *dp
= tolower ((unsigned char) *dp
);
3291 for (up
= dp
; isspace ((unsigned char) *dp
);)
3293 if (dp
== cp
|| *dp
!= '=') {
3294 inform("invalid parameter in message %s's %s: "
3295 "field\n%*sparameter %s (error detected at offset %d)",
3296 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3301 * To handle RFC 2231, we have to deal with the following extensions:
3303 * name*=encoded-value
3304 * name*<N>=part-N-of-a-parameter-value
3305 * name*<N>*=encoded-part-N-of-a-parameter-value
3308 * If there's a * right before the equal sign, it's encoded.
3309 * If there's a * and one or more digits, then it's section N.
3311 * Remember we can have one or the other, or both. cp points to
3312 * beginning of name, up points past the last character in the
3316 for (vp
= cp
; vp
< up
; vp
++) {
3317 if (*vp
== '*' && vp
< up
- 1) {
3321 if (*vp
== '*' && vp
== up
- 1) {
3323 } else if (partial
) {
3324 if (isdigit((unsigned char) *vp
))
3325 index
= *vp
- '0' + index
* 10;
3327 inform("invalid parameter index in message %s's "
3328 "%s: field\n%*s(parameter %s)", filename
,
3329 fieldname
, strlen(invo_name
) + 2, "", cp
);
3338 * Break out the parameter name and value sections and allocate
3342 nameptr
= mh_xmalloc(len
+ 1);
3343 strncpy(nameptr
, cp
, len
);
3344 nameptr
[len
] = '\0';
3346 for (dp
++; isspace ((unsigned char) *dp
);)
3351 * Single quotes delimit the character set and language tag.
3352 * They are required on the first section (or a complete
3357 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3363 charset
= mh_xmalloc(len
+ 1);
3364 strncpy(charset
, dp
, len
);
3365 charset
[len
] = '\0';
3371 inform("missing charset in message %s's %s: "
3372 "field\n%*s(parameter %s)", filename
, fieldname
,
3373 strlen(invo_name
) + 2, "", nameptr
);
3379 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3386 lang
= mh_xmalloc(len
+ 1);
3387 strncpy(lang
, dp
, len
);
3394 inform("missing language tag in message %s's %s: "
3395 "field\n%*s(parameter %s)", filename
, fieldname
,
3396 strlen(invo_name
) + 2, "", nameptr
);
3406 * At this point vp should be pointing at the beginning
3407 * of the encoded value/section. Continue until we reach
3408 * the end or get whitespace. But first, calculate the
3409 * length so we can allocate the correct buffer size.
3412 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3414 if (*(vp
+ 1) == '\0' ||
3415 !isxdigit((unsigned char) *(vp
+ 1)) ||
3416 *(vp
+ 2) == '\0' ||
3417 !isxdigit((unsigned char) *(vp
+ 2))) {
3418 inform("invalid encoded sequence in message "
3419 "%s's %s: field\n%*s(parameter %s)",
3420 filename
, fieldname
, strlen(invo_name
) + 2,
3432 up
= valptr
= mh_xmalloc(len
+ 1);
3434 for (vp
= dp
; istoken(*vp
); vp
++) {
3436 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3447 * A "normal" string. If it's got a leading quote, then we
3448 * strip the quotes out. Otherwise go until we reach the end
3449 * or get whitespace. Note we scan it twice; once to get the
3450 * length, then the second time copies it into the destination
3457 for (cp
= dp
+ 1;;) {
3461 inform("invalid quoted-string in message %s's %s: "
3462 "field\n%*s(parameter %s)", filename
,
3463 fieldname
, strlen(invo_name
) + 2, "", nameptr
);
3483 for (cp
= dp
; istoken (*cp
); cp
++) {
3488 valptr
= mh_xmalloc(len
+ 1);
3492 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3500 strncpy(valptr
, cp
= dp
, len
);
3508 * If 'partial' is set, we don't allocate a parameter now. We
3509 * put it on the parameter linked list to be reassembled later.
3511 * "phead" points to a list of all parameters we need to reassemble.
3512 * Each parameter has a list of sections. We insert the sections in
3517 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3518 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3533 * Insert this into the section linked list
3541 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3542 sp
->next
= pp
->sechead
;
3545 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3546 if (sp2
->index
== sp
->index
) {
3547 inform("duplicate index (%d) in message "
3548 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3549 filename
, fieldname
, strlen(invo_name
) + 2, "",
3553 if (sp2
->index
< sp
->index
&&
3554 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3555 sp
->next
= sp2
->next
;
3562 inform("Internal error: cannot insert partial "
3563 "param in message %s's %s: field\n%*s(parameter %s)",
3564 filename
, fieldname
, strlen(invo_name
) + 2, "",
3571 * Save our charset and lang tags.
3574 if (index
== 0 && encoded
) {
3575 mh_xfree(pp
->charset
);
3576 pp
->charset
= charset
;
3581 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3582 pm
->pm_charset
= charset
;
3586 while (isspace ((unsigned char) *cp
))
3590 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3596 * Now that we're done, reassemble all of the partial parameters.
3599 for (pp
= phead
; pp
!= NULL
; ) {
3603 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3604 if (sp
->index
!= pindex
++) {
3605 inform("missing section %d for parameter in "
3606 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3607 filename
, fieldname
, strlen(invo_name
) + 2, "",
3614 p
= q
= mh_xmalloc(tlen
+ 1);
3615 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3616 memcpy(q
, sp
->value
, sp
->len
);
3626 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3627 pm
->pm_charset
= pp
->charset
;
3628 pm
->pm_lang
= pp
->lang
;
3639 * Return the charset for a particular content type.
3643 content_charset (CT ct
) {
3644 char *ret_charset
= NULL
;
3646 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3648 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3653 * Create a string based on a list of output parameters. Assume that this
3654 * parameter string will be appended to an existing header, so start out
3655 * with the separator (;). Perform RFC 2231 encoding when necessary.
3659 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3661 char *paramout
= NULL
;
3662 char line
[CPERLIN
* 2], *q
;
3663 int curlen
, index
, cont
, encode
, i
;
3664 size_t valoff
, numchars
;
3666 while (params
!= NULL
) {
3672 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3675 if (strlen(params
->pm_name
) > CPERLIN
) {
3676 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3681 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3684 * Loop until we get a parameter that fits within a line. We
3685 * assume new lines start with a tab, so check our overflow based
3695 * At this point we're definitely continuing the line, so
3696 * be sure to include the parameter name and section index.
3699 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3700 params
->pm_name
, index
);
3703 * Both of these functions do a NUL termination
3707 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3708 numchars
, valoff
, index
);
3710 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3720 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3725 * "line" starts with a ;\n\t, so that doesn't count against
3726 * the length. But add 8 since it starts with a tab; that's
3727 * how we end up with 5.
3730 initialwidth
= strlen(line
) + 5;
3733 * At this point the line should be built, so add it to our
3734 * current output buffer.
3737 paramout
= add(line
, paramout
);
3741 * If this won't fit on the line, start a new one. Save room in
3742 * case we need a semicolon on the end
3745 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3757 * At this point, we're either finishing a continued parameter, or
3758 * we're working on a new one.
3762 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3763 params
->pm_name
, index
);
3765 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3770 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3771 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3773 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3774 strlen(params
->pm_value
+ valoff
), valoff
);
3781 paramout
= add(line
, paramout
);
3782 initialwidth
+= strlen(line
);
3784 params
= params
->pm_next
;
3788 *offsetout
= initialwidth
;
3794 * Calculate the size of a parameter.
3798 * pm - The parameter being output
3799 * index - If continuing the parameter, the index of the section
3801 * valueoff - The current offset into the parameter value that we're
3802 * working on (previous sections have consumed valueoff bytes).
3803 * encode - Set if we should perform encoding on this parameter section
3804 * (given that we're consuming bytesfit bytes).
3805 * cont - Set if the remaining data in value will not fit on a single
3806 * line and will need to be continued.
3807 * bytesfit - The number of bytes that we can consume from the parameter
3808 * value and still fit on a completely new line. The
3809 * calculation assumes the new line starts with a tab,
3810 * includes the parameter name and any encoding, and fits
3811 * within CPERLIN bytes. Will always be at least 1.
3815 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3818 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3819 size_t len
= 0, fit
= 0;
3820 int fitlimit
= 0, eightbit
, maxfit
;
3825 * Add up the length. First, start with the parameter name.
3828 len
= strlen(pm
->pm_name
);
3831 * Scan the parameter value and see if we need to do encoding for this
3835 eightbit
= contains8bit(start
, NULL
);
3838 * Determine if we need to encode this section. Encoding is necessary if:
3840 * - There are any 8-bit characters at all and we're on the first
3842 * - There are 8-bit characters within N bytes of our section start.
3843 * N is calculated based on the number of bytes it would take to
3844 * reach CPERLIN. Specifically:
3845 * 8 (starting tab) +
3846 * strlen(param name) +
3847 * 4 ('* for section marker, '=', opening/closing '"')
3849 * is the number of bytes used by everything that isn't part of the
3850 * value. So that gets subtracted from CPERLIN.
3853 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3854 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3855 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3859 len
++; /* Add in equal sign */
3863 * We're using maxfit as a marker for how many characters we can
3864 * fit into the line. Bump it by two because we're not using quotes
3871 * If we don't have a charset or language tag in this parameter,
3875 if (! pm
->pm_charset
) {
3876 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3877 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3878 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3879 "local character set is US-ASCII", pm
->pm_name
);
3882 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3884 len
++; /* For the encoding marker */
3887 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3892 * We know we definitely need to include an index. maxfit already
3893 * includes the section marker.
3895 len
+= strlen(indexchar
);
3897 for (p
= start
; *p
!= '\0'; p
++) {
3898 if (isparamencode(*p
)) {
3906 * Just so there's no confusion: maxfit is counting OUTPUT
3907 * characters (post-encoding). fit is counting INPUT characters.
3909 if (! fitlimit
&& maxfit
>= 0)
3911 else if (! fitlimit
)
3916 * Calculate the string length, but add room for quoting \
3917 * and " if necessary. Also account for quotes at beginning
3920 for (p
= start
; *p
!= '\0'; p
++) {
3931 if (! fitlimit
&& maxfit
>= 0)
3933 else if (! fitlimit
)
3950 * Output an encoded parameter string.
3954 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3955 size_t valueoff
, int index
)
3957 size_t outlen
= 0, n
;
3958 char *endptr
= output
+ len
, *p
;
3961 * First, output the marker for an encoded string.
3969 * If the index is 0, output the character set and language tag.
3970 * If theses were NULL, they should have already been filled in
3975 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3979 if (output
> endptr
) {
3980 inform("Internal error: parameter buffer overflow");
3986 * Copy over the value, encoding if necessary
3989 p
= pm
->pm_value
+ valueoff
;
3990 while (valuelen
-- > 0) {
3991 if (isparamencode(*p
)) {
3992 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
3999 if (output
> endptr
) {
4000 inform("Internal error: parameter buffer overflow");
4011 * Output a "normal" parameter, without encoding. Be sure to escape
4012 * quotes and backslashes if necessary.
4016 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4020 char *endptr
= output
+ len
, *p
;
4026 p
= pm
->pm_value
+ valueoff
;
4028 while (valuelen
-- > 0) {
4039 if (output
> endptr
) {
4040 inform("Internal error: parameter buffer overflow");
4045 if (output
- 2 > endptr
) {
4046 inform("Internal error: parameter buffer overflow");
4057 * Add a parameter to the parameter linked list
4061 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4066 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4067 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4070 (*last
)->pm_next
= pm
;
4081 * Either replace a current parameter with a new value, or add the parameter
4082 * to the parameter linked list.
4086 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4090 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4091 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4093 * If nocopy is set, it's assumed that we own both name
4094 * and value. We don't need name, so we discard it now.
4099 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4104 return add_param(first
, last
, name
, value
, nocopy
);
4108 * Retrieve a parameter value from a parameter linked list. If the parameter
4109 * value needs converted to the local character set, do that now.
4113 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4115 while (first
!= NULL
) {
4116 if (strcasecmp(name
, first
->pm_name
) == 0) {
4118 return first
->pm_value
;
4119 return getcpy(get_param_value(first
, replace
));
4121 first
= first
->pm_next
;
4128 * Return a parameter value, converting to the local character set if
4132 char *get_param_value(PM pm
, char replace
)
4134 static char buffer
[4096]; /* I hope no parameters are larger */
4135 size_t bufsize
= sizeof(buffer
);
4140 ICONV_CONST
char *p
;
4141 #else /* HAVE_ICONV */
4143 #endif /* HAVE_ICONV */
4148 * If we don't have a character set indicated, it's assumed to be
4149 * US-ASCII. If it matches our character set, we don't need to convert
4153 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4154 strlen(pm
->pm_charset
))) {
4155 return pm
->pm_value
;
4159 * In this case, we need to convert. If we have iconv support, use
4160 * that. Otherwise, go through and simply replace every non-ASCII
4161 * character with the substitution character.
4166 bufsize
= sizeof(buffer
);
4167 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4169 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4170 if (cd
== (iconv_t
) -1) {
4174 inbytes
= strlen(pm
->pm_value
);
4178 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4179 if (errno
!= EILSEQ
) {
4184 * Reset shift state, substitute our character,
4185 * try to restart conversion.
4188 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4201 for (++p
, --inbytes
;
4202 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4221 #endif /* HAVE_ICONV */
4224 * Take everything non-ASCII and substitute the replacement character
4228 bufsize
= sizeof(buffer
);
4229 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4230 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))