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>
17 #include <h/mhcachesbr.h>
18 #include "../sbr/m_mktemp.h"
22 #endif /* HAVE_ICONV */
23 #include "../sbr/base64.h"
28 int checksw
= 0; /* check Content-MD5 field */
31 * These are for mhfixmsg to:
32 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
34 * 2) Suppress the warning about bogus multipart content, and report it.
35 * 3) Suppress the warning about extraneous trailing ';' in header parameter
38 bool skip_mp_cte_check
;
39 bool suppress_bogus_mp_content_warning
;
40 bool bogus_mp_content
;
41 bool suppress_extraneous_trailing_semicolon_warning
;
44 * By default, suppress warning about multiple MIME-Version header fields.
46 bool suppress_multiple_mime_version_warning
= true;
48 /* list of preferred type/subtype pairs, for -prefer */
49 char *preferred_types
[NPREFS
];
50 char *preferred_subtypes
[NPREFS
];
55 * Structures for TEXT messages
57 struct k2v SubText
[] = {
58 { "plain", TEXT_PLAIN
},
59 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
60 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
61 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
64 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
67 * Structures for MULTIPART messages
69 struct k2v SubMultiPart
[] = {
70 { "mixed", MULTI_MIXED
},
71 { "alternative", MULTI_ALTERNATE
},
72 { "digest", MULTI_DIGEST
},
73 { "parallel", MULTI_PARALLEL
},
74 { "related", MULTI_RELATED
},
75 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
79 * Structures for MESSAGE messages
81 struct k2v SubMessage
[] = {
82 { "rfc822", MESSAGE_RFC822
},
83 { "partial", MESSAGE_PARTIAL
},
84 { "external-body", MESSAGE_EXTERNAL
},
85 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
89 * Structure for APPLICATION messages
91 struct k2v SubApplication
[] = {
92 { "octet-stream", APPLICATION_OCTETS
},
93 { "postscript", APPLICATION_POSTSCRIPT
},
94 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
98 * Mapping of names of CTE types in mhbuild directives
100 static struct k2v EncodingType
[] = {
103 { "q-p", CE_QUOTED
},
104 { "quoted-printable", CE_QUOTED
},
105 { "b64", CE_BASE64
},
106 { "base64", CE_BASE64
},
114 static CT
get_content (FILE *, char *, int);
115 static int get_comment (const char *, const char *, char **, char **);
117 static int InitGeneric (CT
);
118 static int InitText (CT
);
119 static int InitMultiPart (CT
);
120 static void reverse_parts (CT
);
121 static void prefer_parts(CT ct
);
122 static int InitMessage (CT
);
123 static int InitApplication (CT
);
124 static int init_encoding (CT
, OpenCEFunc
);
125 static unsigned long size_encoding (CT
);
126 static int InitBase64 (CT
);
127 static int openBase64 (CT
, char **);
128 static int InitQuoted (CT
);
129 static int openQuoted (CT
, char **);
130 static int Init7Bit (CT
);
131 static int openExternal (CT
, CT
, CE
, char **, int *);
132 static int InitFile (CT
);
133 static int openFile (CT
, char **);
134 static int InitFTP (CT
);
135 static int openFTP (CT
, char **);
136 static int InitMail (CT
);
137 static int openMail (CT
, char **);
138 static int readDigest (CT
, char *);
139 static int get_leftover_mp_content (CT
, int);
140 static int InitURL (CT
);
141 static int openURL (CT
, char **);
142 static int parse_header_attrs (const char *, const char *, char **, PM
*,
144 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
145 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
146 static int get_dispo (char *, CT
, int);
148 struct str2init str2cts
[] = {
149 { "application", CT_APPLICATION
, InitApplication
},
150 { "audio", CT_AUDIO
, InitGeneric
},
151 { "image", CT_IMAGE
, InitGeneric
},
152 { "message", CT_MESSAGE
, InitMessage
},
153 { "multipart", CT_MULTIPART
, InitMultiPart
},
154 { "text", CT_TEXT
, InitText
},
155 { "video", CT_VIDEO
, InitGeneric
},
156 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
157 { NULL
, CT_UNKNOWN
, NULL
},
160 struct str2init str2ces
[] = {
161 { "base64", CE_BASE64
, InitBase64
},
162 { "quoted-printable", CE_QUOTED
, InitQuoted
},
163 { "8bit", CE_8BIT
, Init7Bit
},
164 { "7bit", CE_7BIT
, Init7Bit
},
165 { "binary", CE_BINARY
, Init7Bit
},
166 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
167 { NULL
, CE_UNKNOWN
, NULL
},
171 * NOTE WELL: si_key MUST NOT have value of NOTOK
173 * si_val is 1 if access method is anonymous.
175 struct str2init str2methods
[] = {
176 { "afs", 1, InitFile
},
177 { "anon-ftp", 1, InitFTP
},
178 { "ftp", 0, InitFTP
},
179 { "local-file", 0, InitFile
},
180 { "mail-server", 0, InitMail
},
181 { "url", 0, InitURL
},
187 * Main entry point for parsing a MIME message or file.
188 * It returns the Content structure for the top level
189 * entity in the file.
193 parse_mime (char *file
)
202 bogus_mp_content
= false;
205 * Check if file is actually standard input
207 if ((is_stdin
= !(strcmp (file
, "-")))) {
208 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
210 advise("mhparse", "unable to create temporary file in %s",
214 file
= mh_xstrdup(tfile
);
216 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
217 if (fwrite(buffer
, 1, n
, fp
) != n
) {
218 (void) m_unlink (file
);
219 advise (file
, "error copying to temporary file");
225 if (ferror (stdin
)) {
226 (void) m_unlink (file
);
227 advise ("stdin", "error reading");
231 (void) m_unlink (file
);
232 advise (file
, "error writing");
235 fseek (fp
, 0L, SEEK_SET
);
236 } else if (stat (file
, &statbuf
) == NOTOK
) {
237 advise (file
, "unable to stat");
239 } else if (S_ISDIR(statbuf
.st_mode
)) {
240 /* Don't try to parse a directory. */
241 inform("%s is a directory", file
);
243 } else if ((fp
= fopen (file
, "r")) == NULL
) {
244 advise (file
, "unable to read");
248 if (!(ct
= get_content (fp
, file
, 1))) {
250 (void) m_unlink (file
);
251 inform("unable to decode %s", file
);
256 ct
->c_unlink
= 1; /* temp file to remove */
260 if (ct
->c_end
== 0L) {
261 fseek (fp
, 0L, SEEK_END
);
262 ct
->c_end
= ftell (fp
);
265 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
277 * Main routine for reading/parsing the headers
278 * of a message content.
280 * toplevel = 1 # we are at the top level of the message
281 * toplevel = 0 # we are inside message type or multipart type
282 * # other than multipart/digest
283 * toplevel = -1 # we are inside multipart/digest
284 * NB: on failure we will fclose(in)!
288 get_content (FILE *in
, char *file
, int toplevel
)
291 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
295 m_getfld_state_t gstate
;
297 /* allocate the content structure */
300 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
301 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
304 * Parse the header fields for this
305 * content into a linked list.
307 gstate
= m_getfld_state_init(in
);
308 m_getfld_track_filepos2(&gstate
);
309 for (compnum
= 1;;) {
310 int bufsz
= sizeof buf
;
311 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
316 /* get copies of the buffers */
317 np
= mh_xstrdup(name
);
318 vp
= mh_xstrdup(buf
);
320 /* if necessary, get rest of field */
321 while (state
== FLDPLUS
) {
323 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
324 vp
= add (buf
, vp
); /* add to previous value */
327 /* Now add the header data to the list */
328 add_header (ct
, np
, vp
);
330 /* continue, to see if this isn't the last header field */
331 ct
->c_begin
= ftell (in
) + 1;
335 /* There are two cases. The unusual one is when there is no
336 * blank line between the headers and the body. This is
337 * indicated by the name of the header starting with `:'.
339 * For both cases, normal first, `1' is the desired c_begin
340 * file position for the start of the body, and `2' is the
341 * file position when buf is returned.
343 * f o o : b a r \n \n b o d y \n bufsz = 6
345 * f o o : b a r \n b o d y \n bufsz = 4
348 * For the normal case, bufsz includes the
349 * header-terminating `\n', even though it is not in buf,
350 * but bufsz isn't affected when it's missing in the unusual
352 if (name
[0] == ':') {
353 ct
->c_begin
= ftell(in
) - bufsz
;
355 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
360 ct
->c_begin
= ftell (in
);
365 adios (NULL
, "message format error in component #%d", compnum
);
368 adios (NULL
, "getfld() returned %d", state
);
371 /* break out of the loop */
374 m_getfld_state_destroy (&gstate
);
377 * Read the content headers. We will parse the
378 * MIME related header fields into their various
379 * structures and set internal flags related to
380 * content type/subtype, etc.
383 hp
= ct
->c_first_hf
; /* start at first header field */
385 /* Get MIME-Version field */
386 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
391 vrsn
= mh_xstrdup(FENDNULL(hp
->value
));
393 /* Now, cleanup this field */
396 while (isspace ((unsigned char) *cp
))
398 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
400 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
401 if (!isspace ((unsigned char) *dp
))
405 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
408 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
411 for (dp
= cp
; istoken (*dp
); dp
++)
415 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
418 inform("message %s has unknown value for %s: field (%s), continuing...",
419 ct
->c_file
, VRSN_FIELD
, cp
);
424 if (! suppress_multiple_mime_version_warning
)
425 inform("message %s has multiple %s: fields",
426 ct
->c_file
, VRSN_FIELD
);
430 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
431 /* Get Content-Type field */
432 struct str2init
*s2i
;
433 CI ci
= &ct
->c_ctinfo
;
435 /* Check if we've already seen a Content-Type header */
437 inform("message %s has multiple %s: fields",
438 ct
->c_file
, TYPE_FIELD
);
442 /* Parse the Content-Type field */
443 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
447 * Set the Init function and the internal
448 * flag for this content type.
450 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
451 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
453 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
455 ct
->c_type
= s2i
->si_val
;
456 ct
->c_ctinitfnx
= s2i
->si_init
;
458 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
459 /* Get Content-Transfer-Encoding field */
461 struct str2init
*s2i
;
464 * Check if we've already seen the
465 * Content-Transfer-Encoding field
468 inform("message %s has multiple %s: fields",
469 ct
->c_file
, ENCODING_FIELD
);
473 /* get copy of this field */
474 ct
->c_celine
= cp
= mh_xstrdup(FENDNULL(hp
->value
));
476 while (isspace ((unsigned char) *cp
))
478 for (dp
= cp
; istoken (*dp
); dp
++)
484 * Find the internal flag and Init function
485 * for this transfer encoding.
487 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
488 if (!strcasecmp (cp
, s2i
->si_key
))
490 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
493 ct
->c_encoding
= s2i
->si_val
;
495 /* Call the Init function for this encoding */
496 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
499 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
500 /* Get Content-MD5 field */
506 if (ct
->c_digested
) {
507 inform("message %s has multiple %s: fields",
508 ct
->c_file
, MD5_FIELD
);
512 ep
= cp
= mh_xstrdup(FENDNULL(hp
->value
)); /* get a copy */
514 while (isspace ((unsigned char) *cp
))
516 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
518 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
519 if (!isspace ((unsigned char) *dp
))
523 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
526 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
531 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
539 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
540 /* Get Content-ID field */
541 ct
->c_id
= add (hp
->value
, ct
->c_id
);
543 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
544 /* Get Content-Description field */
545 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
547 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
548 /* Get Content-Disposition field */
549 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
554 hp
= hp
->next
; /* next header field */
558 * Check if we saw a Content-Type field.
559 * If not, then assign a default value for
560 * it, and the Init function.
564 * If we are inside a multipart/digest message,
565 * so default type is message/rfc822
568 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
570 ct
->c_type
= CT_MESSAGE
;
571 ct
->c_ctinitfnx
= InitMessage
;
574 * Else default type is text/plain
576 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
578 ct
->c_type
= CT_TEXT
;
579 ct
->c_ctinitfnx
= InitText
;
583 /* Use default Transfer-Encoding, if necessary */
585 ct
->c_encoding
= CE_7BIT
;
598 * small routine to add header field to list
602 add_header (CT ct
, char *name
, char *value
)
606 /* allocate header field structure */
609 /* link data into header structure */
614 /* link header structure into the list */
615 if (ct
->c_first_hf
== NULL
) {
616 ct
->c_first_hf
= hp
; /* this is the first */
619 ct
->c_last_hf
->next
= hp
; /* add it to the end */
628 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
629 * directives. Fills in the information of the CTinfo structure.
632 get_ctinfo (char *cp
, CT ct
, int magic
)
641 /* store copy of Content-Type line */
642 cp
= ct
->c_ctline
= mh_xstrdup(FENDNULL(cp
));
644 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
647 /* change newlines to spaces */
648 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
651 /* trim trailing spaces */
652 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
653 if (!isspace ((unsigned char) *dp
))
658 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
660 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
661 &ci
->ci_comment
) == NOTOK
)
664 for (dp
= cp
; istoken (*dp
); dp
++)
668 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
673 inform("invalid %s: field in message %s (empty type)",
674 TYPE_FIELD
, ct
->c_file
);
677 to_lower(ci
->ci_type
);
679 while (isspace ((unsigned char) *cp
))
682 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
683 &ci
->ci_comment
) == NOTOK
)
688 ci
->ci_subtype
= mh_xstrdup("");
693 while (isspace ((unsigned char) *cp
))
696 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
697 &ci
->ci_comment
) == NOTOK
)
700 for (dp
= cp
; istoken (*dp
); dp
++)
704 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
708 if (!*ci
->ci_subtype
) {
709 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
710 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
713 to_lower(ci
->ci_subtype
);
716 while (isspace ((unsigned char) *cp
))
719 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
720 &ci
->ci_comment
) == NOTOK
)
723 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
724 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
725 &ci
->ci_comment
)) != OK
) {
726 return status
== NOTOK
? NOTOK
: OK
;
730 * Get any <Content-Id> given in buffer
732 if (magic
&& *cp
== '<') {
735 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
736 inform("invalid ID in message %s", ct
->c_file
);
742 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
748 while (isspace ((unsigned char) *cp
))
753 * Get any [Content-Description] given in buffer.
755 if (magic
&& *cp
== '[') {
757 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
761 inform("invalid description in message %s", ct
->c_file
);
769 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
775 while (isspace ((unsigned char) *cp
))
780 * Get any {Content-Disposition} given in buffer.
782 if (magic
&& *cp
== '{') {
784 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
788 inform("invalid disposition in message %s", ct
->c_file
);
796 if (get_dispo(cp
, ct
, 1) != OK
)
802 while (isspace ((unsigned char) *cp
))
807 * Get any extension directives (right now just the content transfer
808 * encoding, but maybe others) that we care about.
811 if (magic
&& *cp
== '*') {
813 * See if it's a CTE we match on
818 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
822 inform("invalid null transfer encoding specification");
829 ct
->c_reqencoding
= CE_UNKNOWN
;
831 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
832 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
833 ct
->c_reqencoding
= kv
->kv_value
;
838 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
839 inform("invalid CTE specification: \"%s\"", dp
);
843 while (isspace ((unsigned char) *cp
))
848 * Check if anything is left over
852 ci
->ci_magic
= mh_xstrdup(cp
);
854 /* If there is a Content-Disposition header and it doesn't
855 have a *filename=, extract it from the magic contents.
856 The r1bindex call skips any leading directory
858 if (ct
->c_dispo_type
&&
859 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
860 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
861 r1bindex(ci
->ci_magic
, '/'), 0);
865 inform("extraneous information in message %s's %s: field\n"
866 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
874 * Parse out a Content-Disposition header. A lot of this is cribbed from
878 get_dispo (char *cp
, CT ct
, int buildflag
)
880 char *dp
, *dispoheader
;
885 * Save the whole copy of the Content-Disposition header, unless we're
886 * processing a mhbuild directive. A NULL c_dispo will be a flag to
887 * mhbuild that the disposition header needs to be generated at that
891 dispoheader
= cp
= mh_xstrdup(FENDNULL(cp
));
893 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
896 /* change newlines to spaces */
897 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
900 /* trim trailing spaces */
901 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
902 if (!isspace ((unsigned char) *dp
))
907 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
909 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
915 for (dp
= cp
; istoken (*dp
); dp
++)
919 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
923 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
926 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
927 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
929 if (status
== NOTOK
) {
934 inform("extraneous information in message %s's %s: field\n (%s)",
935 ct
->c_file
, DISPO_FIELD
, cp
);
941 ct
->c_dispo
= dispoheader
;
948 get_comment (const char *filename
, const char *fieldname
, char **ap
,
953 char c
, buffer
[BUFSIZ
], *dp
;
963 inform("invalid comment in message %s's %s: field",
964 filename
, fieldname
);
969 if ((c
= *cp
++) == '\0')
992 if ((dp
= *commentp
)) {
993 *commentp
= concat (dp
, " ", buffer
, NULL
);
996 *commentp
= mh_xstrdup(buffer
);
1000 while (isspace ((unsigned char) *cp
))
1011 * Handles content types audio, image, and video.
1012 * There's not much to do right here.
1020 return OK
; /* not much to do here */
1031 char buffer
[BUFSIZ
];
1036 CI ci
= &ct
->c_ctinfo
;
1038 /* check for missing subtype */
1039 if (!*ci
->ci_subtype
)
1040 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1043 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1045 /* allocate text character set structure */
1047 ct
->c_ctparams
= (void *) t
;
1049 /* scan for charset parameter */
1050 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1051 if (!strcasecmp (pm
->pm_name
, "charset"))
1054 /* check if content specified a character set */
1056 chset
= pm
->pm_value
;
1057 t
->tx_charset
= CHARSET_SPECIFIED
;
1059 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1063 * If we can not handle character set natively,
1064 * then check profile for string to modify the
1065 * terminal or display method.
1067 * termproc is for mhshow, though mhlist -debug prints it, too.
1069 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1070 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1071 if ((cp
= context_find (buffer
)))
1072 ct
->c_termproc
= mh_xstrdup(cp
);
1084 InitMultiPart (CT ct
)
1094 struct multipart
*m
;
1095 struct part
*part
, **next
;
1096 CI ci
= &ct
->c_ctinfo
;
1101 * The encoding for multipart messages must be either
1102 * 7bit, 8bit, or binary (per RFC 2045).
1104 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1105 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1106 /* Copy the Content-Transfer-Encoding header field body so we can
1107 remove any trailing whitespace and leading blanks from it. */
1108 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1110 bp
= cte
+ strlen (cte
) - 1;
1111 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1112 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1114 inform("\"%s/%s\" type in message %s must be encoded in\n"
1115 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1116 "mhfixmsg -fixcte can fix it, or\n"
1117 "manually edit the file and change the \"%s\"\n"
1118 "Content-Transfer-Encoding to one of those. For now, continuing...",
1119 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1126 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1129 * Check for "boundary" parameter, which is
1130 * required for multipart messages.
1133 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1134 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1140 /* complain if boundary parameter is missing */
1142 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1143 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1147 /* allocate primary structure for multipart info */
1149 ct
->c_ctparams
= (void *) m
;
1151 /* check if boundary parameter contains only whitespace characters */
1152 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1155 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1156 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1160 /* remove trailing whitespace from boundary parameter */
1161 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1162 if (!isspace ((unsigned char) *dp
))
1166 /* record boundary separators */
1167 m
->mp_start
= concat (bp
, "\n", NULL
);
1168 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1170 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1171 advise (ct
->c_file
, "unable to open for reading");
1175 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1177 next
= &m
->mp_parts
;
1181 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1186 if (bufp
[0] != '-' || bufp
[1] != '-')
1189 if (strcmp (bufp
+ 2, m
->mp_start
))
1194 next
= &part
->mp_next
;
1196 if (!(p
= get_content (fp
, ct
->c_file
,
1197 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1205 fseek (fp
, pos
, SEEK_SET
);
1208 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1212 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1213 if (p
->c_end
< p
->c_begin
)
1214 p
->c_begin
= p
->c_end
;
1219 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1224 if (! suppress_bogus_mp_content_warning
) {
1225 inform("bogus multipart content in message %s", ct
->c_file
);
1227 bogus_mp_content
= true;
1229 if (!inout
&& part
) {
1231 p
->c_end
= ct
->c_end
;
1233 if (p
->c_begin
>= p
->c_end
) {
1234 for (next
= &m
->mp_parts
; *next
!= part
;
1235 next
= &((*next
)->mp_next
))
1244 /* reverse the order of the parts for multipart/alternative */
1245 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1251 * label all subparts with part number, and
1252 * then initialize the content of the subpart.
1257 char partnam
[BUFSIZ
];
1260 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1261 pp
= partnam
+ strlen (partnam
);
1266 for (part
= m
->mp_parts
, partnum
= 1; part
;
1267 part
= part
->mp_next
, partnum
++) {
1270 sprintf (pp
, "%d", partnum
);
1271 p
->c_partno
= mh_xstrdup(partnam
);
1273 /* initialize the content of the subparts */
1274 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1283 get_leftover_mp_content (ct
, 1);
1284 get_leftover_mp_content (ct
, 0);
1294 * reverse the order of the parts of a multipart/alternative,
1295 * presumably to put the "most favored" alternative first, for
1296 * ease of choosing/displaying it later on. from a mail message on
1297 * nmh-workers, from kenh:
1298 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1299 * see code in mhn that did the same thing... According to the RCS
1300 * logs, that code was around from the initial checkin of mhn.c by
1301 * John Romine in 1992, which is as far back as we have."
1304 reverse_parts (CT ct
)
1306 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1310 /* Reverse the order of its parts by walking the mp_parts list
1311 and pushing each node to the front. */
1312 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1313 next
= part
->mp_next
;
1314 part
->mp_next
= m
->mp_parts
;
1320 move_preferred_part (CT ct
, char *type
, char *subtype
)
1322 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1323 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1327 /* move the matching part(s) to the head of the list: walk the
1328 * list of parts, move matching parts to a new list (maintaining
1329 * their order), and finally, concatenate the old list onto the
1336 head
->mp_next
= m
->mp_parts
;
1337 nhead
->mp_next
= NULL
;
1341 part
= head
->mp_next
;
1342 while (part
!= NULL
) {
1343 ci
= &part
->mp_part
->c_ctinfo
;
1344 if (!strcasecmp(ci
->ci_type
, type
) &&
1345 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1346 prev
->mp_next
= part
->mp_next
;
1347 part
->mp_next
= NULL
;
1348 ntail
->mp_next
= part
;
1350 part
= prev
->mp_next
;
1353 part
= prev
->mp_next
;
1356 ntail
->mp_next
= head
->mp_next
;
1357 m
->mp_parts
= nhead
->mp_next
;
1362 * move parts that match the user's preferences (-prefer) to the head
1363 * of the line. process preferences in reverse so first one given
1364 * ends up first in line
1370 for (i
= 0; i
< npreferred
; i
++)
1371 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1376 /* parse_mime() arranges alternates in reverse (priority) order. This
1377 function can be used to reverse them back. This will put, for
1378 example, a text/plain part before a text/html part in a
1379 multipart/alternative part, for example, where it belongs. */
1381 reverse_alternative_parts (CT ct
) {
1382 if (ct
->c_type
== CT_MULTIPART
) {
1383 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1386 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1390 /* And call recursively on each part of a multipart. */
1391 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1392 reverse_alternative_parts (part
->mp_part
);
1405 CI ci
= &ct
->c_ctinfo
;
1407 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1408 inform("\"%s/%s\" type in message %s should be encoded in "
1409 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1414 /* check for missing subtype */
1415 if (!*ci
->ci_subtype
)
1416 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1419 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1421 switch (ct
->c_subtype
) {
1422 case MESSAGE_RFC822
:
1425 case MESSAGE_PARTIAL
:
1431 ct
->c_ctparams
= (void *) p
;
1433 /* scan for parameters "id", "number", and "total" */
1434 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1435 if (!strcasecmp (pm
->pm_name
, "id")) {
1436 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1439 if (!strcasecmp (pm
->pm_name
, "number")) {
1440 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1441 || p
->pm_partno
< 1) {
1443 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1444 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1445 ct
->c_file
, TYPE_FIELD
);
1450 if (!strcasecmp (pm
->pm_name
, "total")) {
1451 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1460 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1461 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1462 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1468 case MESSAGE_EXTERNAL
:
1476 ct
->c_ctparams
= (void *) e
;
1479 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1480 advise (ct
->c_file
, "unable to open for reading");
1484 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1486 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1494 p
->c_ceopenfnx
= NULL
;
1495 if ((exresult
= params_external (ct
, 0)) != NOTOK
1496 && p
->c_ceopenfnx
== openMail
) {
1500 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1502 content_error (NULL
, ct
,
1503 "empty body for access-type=mail-server");
1507 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1508 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1510 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1512 adios ("failed", "fread");
1515 adios (NULL
, "unexpected EOF from fread");
1518 bp
+= cc
, size
-= cc
;
1525 p
->c_end
= p
->c_begin
;
1530 if (exresult
== NOTOK
)
1532 if (e
->eb_flags
== NOTOK
)
1535 switch (p
->c_type
) {
1540 if (p
->c_subtype
!= MESSAGE_RFC822
)
1544 e
->eb_partno
= ct
->c_partno
;
1546 (*p
->c_ctinitfnx
) (p
);
1561 params_external (CT ct
, int composing
)
1564 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1565 CI ci
= &ct
->c_ctinfo
;
1567 ct
->c_ceopenfnx
= NULL
;
1568 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1569 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1570 struct str2init
*s2i
;
1571 CT p
= e
->eb_content
;
1573 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1574 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1577 e
->eb_access
= pm
->pm_value
;
1578 e
->eb_flags
= NOTOK
;
1579 p
->c_encoding
= CE_EXTERNAL
;
1582 e
->eb_access
= s2i
->si_key
;
1583 e
->eb_flags
= s2i
->si_val
;
1584 p
->c_encoding
= CE_EXTERNAL
;
1586 /* Call the Init function for this external type */
1587 if ((*s2i
->si_init
)(p
) == NOTOK
)
1591 if (!strcasecmp (pm
->pm_name
, "name")) {
1592 e
->eb_name
= pm
->pm_value
;
1595 if (!strcasecmp (pm
->pm_name
, "permission")) {
1596 e
->eb_permission
= pm
->pm_value
;
1599 if (!strcasecmp (pm
->pm_name
, "site")) {
1600 e
->eb_site
= pm
->pm_value
;
1603 if (!strcasecmp (pm
->pm_name
, "directory")) {
1604 e
->eb_dir
= pm
->pm_value
;
1607 if (!strcasecmp (pm
->pm_name
, "mode")) {
1608 e
->eb_mode
= pm
->pm_value
;
1611 if (!strcasecmp (pm
->pm_name
, "size")) {
1612 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1615 if (!strcasecmp (pm
->pm_name
, "server")) {
1616 e
->eb_server
= pm
->pm_value
;
1619 if (!strcasecmp (pm
->pm_name
, "subject")) {
1620 e
->eb_subject
= pm
->pm_value
;
1623 if (!strcasecmp (pm
->pm_name
, "url")) {
1625 * According to RFC 2017, we have to remove all whitespace from
1629 char *u
, *p
= pm
->pm_value
;
1630 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1632 for (; *p
!= '\0'; p
++) {
1633 if (! isspace((unsigned char) *p
))
1640 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1641 e
->eb_body
= getcpy (pm
->pm_value
);
1646 if (!e
->eb_access
) {
1647 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1648 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1661 InitApplication (CT ct
)
1663 CI ci
= &ct
->c_ctinfo
;
1666 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1673 * TRANSFER ENCODINGS
1677 init_encoding (CT ct
, OpenCEFunc openfnx
)
1679 ct
->c_ceopenfnx
= openfnx
;
1680 ct
->c_ceclosefnx
= close_encoding
;
1681 ct
->c_cesizefnx
= size_encoding
;
1688 close_encoding (CT ct
)
1690 CE ce
= &ct
->c_cefile
;
1699 static unsigned long
1700 size_encoding (CT ct
)
1705 CE ce
= &ct
->c_cefile
;
1708 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1709 return (long) st
.st_size
;
1712 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1713 return (long) st
.st_size
;
1717 if (ct
->c_encoding
== CE_EXTERNAL
)
1718 return (ct
->c_end
- ct
->c_begin
);
1721 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1722 return (ct
->c_end
- ct
->c_begin
);
1724 if (fstat (fd
, &st
) != NOTOK
)
1725 size
= (long) st
.st_size
;
1729 (*ct
->c_ceclosefnx
) (ct
);
1741 return init_encoding (ct
, openBase64
);
1746 openBase64 (CT ct
, char **file
)
1749 int fd
, own_ct_fp
= 0;
1750 char *cp
, *buffer
= NULL
;
1751 /* sbeck -- handle suffixes */
1753 CE ce
= &ct
->c_cefile
;
1754 unsigned char *decoded
;
1756 unsigned char digest
[16];
1759 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1764 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1765 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1771 if (*file
== NULL
) {
1774 ce
->ce_file
= mh_xstrdup(*file
);
1778 /* sbeck@cise.ufl.edu -- handle suffixes */
1780 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1781 if (ce
->ce_unlink
) {
1782 /* Create temporary file with filename extension. */
1783 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1784 adios(NULL
, "unable to create temporary file in %s",
1788 ce
->ce_file
= add (cp
, ce
->ce_file
);
1790 } else if (*file
== NULL
) {
1792 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1793 adios(NULL
, "unable to create temporary file in %s",
1796 ce
->ce_file
= mh_xstrdup(tempfile
);
1799 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1800 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1804 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1805 adios (NULL
, "internal error(1)");
1807 buffer
= mh_xmalloc (len
+ 1);
1810 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1811 content_error (ct
->c_file
, ct
, "unable to open for reading");
1817 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1820 switch (cc
= read (fd
, cp
, len
)) {
1822 content_error (ct
->c_file
, ct
, "error reading from");
1826 content_error (NULL
, ct
, "premature eof");
1837 /* decodeBase64() requires null-terminated input. */
1840 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1841 ct
->c_digested
? digest
: NULL
) != OK
)
1846 unsigned char *decoded_p
= decoded
;
1847 for (i
= 0; i
< decoded_len
; ++i
) {
1848 putc (*decoded_p
++, ce
->ce_fp
);
1851 if (ferror (ce
->ce_fp
)) {
1852 content_error (ce
->ce_file
, ct
, "error writing to");
1856 if (ct
->c_digested
) {
1857 if (memcmp(digest
, ct
->c_digest
,
1859 content_error (NULL
, ct
,
1860 "content integrity suspect (digest mismatch) -- continuing");
1863 fprintf (stderr
, "content integrity confirmed\n");
1869 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1871 if (fflush (ce
->ce_fp
)) {
1872 content_error (ce
->ce_file
, ct
, "error writing to");
1876 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1879 *file
= ce
->ce_file
;
1885 return fileno (ce
->ce_fp
);
1892 free_encoding (ct
, 0);
1902 static char hex2nib
[0x80] = {
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1909 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1910 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1911 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1913 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1914 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1915 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1916 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1917 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1918 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1925 return init_encoding (ct
, openQuoted
);
1930 openQuoted (CT ct
, char **file
)
1932 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1938 CE ce
= &ct
->c_cefile
;
1939 /* sbeck -- handle suffixes */
1944 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1949 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1950 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1956 if (*file
== NULL
) {
1959 ce
->ce_file
= mh_xstrdup(*file
);
1963 /* sbeck@cise.ufl.edu -- handle suffixes */
1965 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1966 if (ce
->ce_unlink
) {
1967 /* Create temporary file with filename extension. */
1968 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1969 adios(NULL
, "unable to create temporary file in %s",
1973 ce
->ce_file
= add (cp
, ce
->ce_file
);
1975 } else if (*file
== NULL
) {
1977 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1978 adios(NULL
, "unable to create temporary file in %s",
1981 ce
->ce_file
= mh_xstrdup(tempfile
);
1984 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1985 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1989 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1990 adios (NULL
, "internal error(2)");
1993 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1994 content_error (ct
->c_file
, ct
, "unable to open for reading");
2000 if ((digested
= ct
->c_digested
))
2001 MD5Init (&mdContext
);
2008 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2010 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2011 content_error (NULL
, ct
, "premature eof");
2015 if ((cc
= gotlen
) > len
)
2019 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2020 if (!isspace ((unsigned char) *ep
))
2025 for (; cp
< ep
; cp
++) {
2027 /* in an escape sequence */
2029 /* at byte 1 of an escape sequence */
2030 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2031 /* next is byte 2 */
2034 /* at byte 2 of an escape sequence */
2036 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2037 putc (mask
, ce
->ce_fp
);
2039 MD5Update (&mdContext
, &mask
, 1);
2040 if (ferror (ce
->ce_fp
)) {
2041 content_error (ce
->ce_file
, ct
, "error writing to");
2044 /* finished escape sequence; next may be literal or a new
2045 * escape sequence */
2048 /* on to next byte */
2052 /* not in an escape sequence */
2054 /* starting an escape sequence, or invalid '='? */
2055 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2056 /* "=\n" soft line break, eat the \n */
2060 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2061 /* We don't have 2 bytes left, so this is an invalid
2062 * escape sequence; just show the raw bytes (below). */
2063 } else if (isxdigit ((unsigned char) cp
[1]) &&
2064 isxdigit ((unsigned char) cp
[2])) {
2065 /* Next 2 bytes are hex digits, making this a valid escape
2066 * sequence; let's decode it (above). */
2070 /* One or both of the next 2 is out of range, making this
2071 * an invalid escape sequence; just show the raw bytes
2075 /* Just show the raw byte. */
2076 putc (*cp
, ce
->ce_fp
);
2079 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2081 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2084 if (ferror (ce
->ce_fp
)) {
2085 content_error (ce
->ce_file
, ct
, "error writing to");
2091 content_error (NULL
, ct
,
2092 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2096 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2098 if (fflush (ce
->ce_fp
)) {
2099 content_error (ce
->ce_file
, ct
, "error writing to");
2104 unsigned char digest
[16];
2106 MD5Final (digest
, &mdContext
);
2107 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2109 content_error (NULL
, ct
,
2110 "content integrity suspect (digest mismatch) -- continuing");
2112 fprintf (stderr
, "content integrity confirmed\n");
2115 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2118 *file
= ce
->ce_file
;
2124 return fileno (ce
->ce_fp
);
2127 free_encoding (ct
, 0);
2144 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2147 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2153 open7Bit (CT ct
, char **file
)
2155 int cc
, fd
, len
, own_ct_fp
= 0;
2156 char buffer
[BUFSIZ
];
2157 /* sbeck -- handle suffixes */
2160 CE ce
= &ct
->c_cefile
;
2163 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2168 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2169 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2175 if (*file
== NULL
) {
2178 ce
->ce_file
= mh_xstrdup(*file
);
2182 /* sbeck@cise.ufl.edu -- handle suffixes */
2184 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2185 if (ce
->ce_unlink
) {
2186 /* Create temporary file with filename extension. */
2187 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2188 adios(NULL
, "unable to create temporary file in %s",
2192 ce
->ce_file
= add (cp
, ce
->ce_file
);
2194 } else if (*file
== NULL
) {
2196 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2197 adios(NULL
, "unable to create temporary file in %s",
2200 ce
->ce_file
= mh_xstrdup(tempfile
);
2203 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2204 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2208 if (ct
->c_type
== CT_MULTIPART
) {
2209 CI ci
= &ct
->c_ctinfo
;
2213 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2214 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2215 + 1 + strlen (ci
->ci_subtype
);
2216 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2219 fputs (buffer
, ce
->ce_fp
);
2223 if (ci
->ci_comment
) {
2224 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2225 fputs ("\n\t", ce
->ce_fp
);
2229 putc (' ', ce
->ce_fp
);
2232 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2235 fprintf (ce
->ce_fp
, "\n");
2237 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2239 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2241 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2242 fprintf (ce
->ce_fp
, "\n");
2245 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2246 adios (NULL
, "internal error(3)");
2249 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2250 content_error (ct
->c_file
, ct
, "unable to open for reading");
2256 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2258 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2260 content_error (ct
->c_file
, ct
, "error reading from");
2264 content_error (NULL
, ct
, "premature eof");
2272 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2273 advise ("open7Bit", "fwrite");
2275 if (ferror (ce
->ce_fp
)) {
2276 content_error (ce
->ce_file
, ct
, "error writing to");
2281 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2283 if (fflush (ce
->ce_fp
)) {
2284 content_error (ce
->ce_file
, ct
, "error writing to");
2288 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2291 *file
= ce
->ce_file
;
2296 return fileno (ce
->ce_fp
);
2299 free_encoding (ct
, 0);
2313 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2315 char cachefile
[BUFSIZ
];
2318 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2323 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2324 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2330 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2331 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2332 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2333 ce
->ce_file
= mh_xstrdup(cachefile
);
2337 admonish (cachefile
, "unable to fopen for reading");
2340 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2344 *file
= ce
->ce_file
;
2345 *fd
= fileno (ce
->ce_fp
);
2356 return init_encoding (ct
, openFile
);
2361 openFile (CT ct
, char **file
)
2364 char cachefile
[BUFSIZ
];
2365 struct exbody
*e
= ct
->c_ctexbody
;
2366 CE ce
= &ct
->c_cefile
;
2368 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2380 content_error (NULL
, ct
, "missing name parameter");
2384 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2387 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2388 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2392 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2393 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2394 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2398 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2399 if ((fp
= fopen (cachefile
, "w"))) {
2401 char buffer
[BUFSIZ
];
2402 FILE *gp
= ce
->ce_fp
;
2404 fseek (gp
, 0L, SEEK_SET
);
2406 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2408 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2409 advise ("openFile", "fwrite");
2414 admonish (ce
->ce_file
, "error reading");
2415 (void) m_unlink (cachefile
);
2416 } else if (ferror (fp
)) {
2417 admonish (cachefile
, "error writing");
2418 (void) m_unlink (cachefile
);
2425 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2426 *file
= ce
->ce_file
;
2427 return fileno (ce
->ce_fp
);
2437 return init_encoding (ct
, openFTP
);
2442 openFTP (CT ct
, char **file
)
2444 int cachetype
, caching
, fd
;
2446 char *bp
, *ftp
, *user
, *pass
;
2447 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2449 CE ce
= &ct
->c_cefile
;
2450 static char *username
= NULL
;
2451 static char *password
= NULL
;
2455 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2461 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2472 if (!e
->eb_name
|| !e
->eb_site
) {
2473 content_error (NULL
, ct
, "missing %s parameter",
2474 e
->eb_name
? "site": "name");
2478 /* Get the buffer ready to go */
2480 buflen
= sizeof(buffer
);
2483 * Construct the query message for user
2485 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2491 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2497 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2498 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2503 if (e
->eb_size
> 0) {
2504 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2509 snprintf (bp
, buflen
, "? ");
2512 * Now, check the answer
2514 if (!read_yes_or_no_if_tty (buffer
))
2519 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2523 ruserpass (e
->eb_site
, &username
, &password
, 0);
2528 ce
->ce_unlink
= (*file
== NULL
);
2530 cachefile
[0] = '\0';
2531 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2532 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2533 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2534 if (*file
== NULL
) {
2541 ce
->ce_file
= mh_xstrdup(*file
);
2543 ce
->ce_file
= mh_xstrdup(cachefile
);
2546 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2547 adios(NULL
, "unable to create temporary file in %s",
2550 ce
->ce_file
= mh_xstrdup(tempfile
);
2553 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2554 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2563 vec
[vecp
++] = r1bindex (ftp
, '/');
2564 vec
[vecp
++] = e
->eb_site
;
2567 vec
[vecp
++] = e
->eb_dir
;
2568 vec
[vecp
++] = e
->eb_name
;
2569 vec
[vecp
++] = ce
->ce_file
,
2570 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2571 ? "ascii" : "binary";
2579 adios ("fork", "unable to");
2583 close (fileno (ce
->ce_fp
));
2585 fprintf (stderr
, "unable to exec ");
2591 if (pidXwait (child_id
, NULL
)) {
2592 username
= password
= NULL
;
2602 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2607 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2608 if ((fp
= fopen (cachefile
, "w"))) {
2610 FILE *gp
= ce
->ce_fp
;
2612 fseek (gp
, 0L, SEEK_SET
);
2614 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2616 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2617 advise ("openFTP", "fwrite");
2622 admonish (ce
->ce_file
, "error reading");
2623 (void) m_unlink (cachefile
);
2624 } else if (ferror (fp
)) {
2625 admonish (cachefile
, "error writing");
2626 (void) m_unlink (cachefile
);
2634 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2635 *file
= ce
->ce_file
;
2636 return fileno (ce
->ce_fp
);
2647 return init_encoding (ct
, openMail
);
2652 openMail (CT ct
, char **file
)
2654 int child_id
, fd
, vecp
;
2656 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2657 struct exbody
*e
= ct
->c_ctexbody
;
2658 CE ce
= &ct
->c_cefile
;
2660 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2671 if (!e
->eb_server
) {
2672 content_error (NULL
, ct
, "missing server parameter");
2676 /* Get buffer ready to go */
2678 buflen
= sizeof(buffer
);
2680 /* Now, construct query message */
2681 snprintf (bp
, buflen
, "Retrieve content");
2687 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2693 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2695 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2697 /* Now, check answer */
2698 if (!read_yes_or_no_if_tty (buffer
))
2702 vec
[vecp
++] = r1bindex (mailproc
, '/');
2703 vec
[vecp
++] = e
->eb_server
;
2704 vec
[vecp
++] = "-subject";
2705 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2706 vec
[vecp
++] = "-body";
2707 vec
[vecp
++] = e
->eb_body
;
2713 advise ("fork", "unable to");
2717 execvp (mailproc
, vec
);
2718 fprintf (stderr
, "unable to exec ");
2724 if (pidXwait (child_id
, NULL
) == OK
)
2725 inform("request sent");
2729 if (*file
== NULL
) {
2731 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2732 adios(NULL
, "unable to create temporary file in %s",
2735 ce
->ce_file
= mh_xstrdup(tempfile
);
2738 ce
->ce_file
= mh_xstrdup(*file
);
2742 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2743 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2747 /* showproc is for mhshow and mhstore, though mhlist -debug
2748 * prints it, too. */
2749 mh_xfree(ct
->c_showproc
);
2750 ct
->c_showproc
= mh_xstrdup("true");
2752 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2753 *file
= ce
->ce_file
;
2754 return fileno (ce
->ce_fp
);
2765 return init_encoding (ct
, openURL
);
2770 openURL (CT ct
, char **file
)
2772 struct exbody
*e
= ct
->c_ctexbody
;
2773 CE ce
= &ct
->c_cefile
;
2774 char *urlprog
, *program
;
2775 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2776 int fd
, caching
, cachetype
;
2777 struct msgs_array args
= { 0, 0, NULL
};
2780 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2784 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2788 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2800 content_error(NULL
, ct
, "missing url parameter");
2804 ce
->ce_unlink
= (*file
== NULL
);
2806 cachefile
[0] = '\0';
2808 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2809 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2810 if (*file
== NULL
) {
2817 ce
->ce_file
= mh_xstrdup(*file
);
2819 ce
->ce_file
= mh_xstrdup(cachefile
);
2822 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2823 adios(NULL
, "unable to create temporary file in %s",
2826 ce
->ce_file
= mh_xstrdup(tempfile
);
2829 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2830 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2834 switch (child_id
= fork()) {
2836 adios ("fork", "unable to");
2840 argsplit_msgarg(&args
, urlprog
, &program
);
2841 app_msgarg(&args
, e
->eb_url
);
2842 app_msgarg(&args
, NULL
);
2843 dup2(fileno(ce
->ce_fp
), 1);
2844 close(fileno(ce
->ce_fp
));
2845 execvp(program
, args
.msgs
);
2846 fprintf(stderr
, "Unable to exec ");
2852 if (pidXwait(child_id
, NULL
)) {
2860 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2865 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2866 if ((fp
= fopen(cachefile
, "w"))) {
2868 FILE *gp
= ce
->ce_fp
;
2870 fseeko(gp
, 0, SEEK_SET
);
2872 while ((cc
= fread(buffer
, sizeof(*buffer
),
2873 sizeof(buffer
), gp
)) > 0)
2874 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2875 advise ("openURL", "fwrite");
2881 admonish(ce
->ce_file
, "error reading");
2882 (void) m_unlink (cachefile
);
2889 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2890 *file
= ce
->ce_file
;
2891 return fileno(ce
->ce_fp
);
2896 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2897 * has to be base64 decoded.
2900 readDigest (CT ct
, char *cp
)
2902 unsigned char *digest
;
2905 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2906 const size_t maxlen
= sizeof ct
->c_digest
;
2908 if (strlen ((char *) digest
) <= maxlen
) {
2909 memcpy (ct
->c_digest
, digest
, maxlen
);
2914 fprintf (stderr
, "MD5 digest=");
2915 for (i
= 0; i
< maxlen
; ++i
) {
2916 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2918 fprintf (stderr
, "\n");
2924 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2925 (int) strlen ((char *) digest
));
2935 /* Multipart parts might have content before the first subpart and/or
2936 after the last subpart that hasn't been stored anywhere else, so do
2939 get_leftover_mp_content (CT ct
, int before
/* or after */)
2941 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2943 int found_boundary
= 0;
2949 char *content
= NULL
;
2951 if (! m
) return NOTOK
;
2954 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2956 /* Isolate the beginning of this part to the beginning of the
2957 first subpart and save any content between them. */
2958 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2959 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2960 boundary
= concat ("--", m
->mp_start
, NULL
);
2962 struct part
*last_subpart
= NULL
;
2963 struct part
*subpart
;
2965 /* Go to the last subpart to get its end position. */
2966 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2967 last_subpart
= subpart
;
2970 if (last_subpart
== NULL
) return NOTOK
;
2972 /* Isolate the end of the last subpart to the end of this part
2973 and save any content between them. */
2974 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2975 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2976 boundary
= concat ("--", m
->mp_stop
, NULL
);
2979 /* Back up by 1 to pick up the newline. */
2980 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2982 /* Don't look beyond beginning of first subpart (before) or
2983 next part (after). */
2984 if (read
> max
) bufp
[read
-max
] = '\0';
2987 if (! strcmp (bufp
, boundary
)) {
2991 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2997 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2999 char *old_content
= content
;
3000 content
= concat (content
, bufp
, NULL
);
3004 ? concat ("\n", bufp
, NULL
)
3005 : concat (bufp
, NULL
);
3010 if (found_boundary
|| read
> max
) break;
3012 if (read
> max
) break;
3016 /* Skip the newline if that's all there is. */
3020 /* Remove trailing newline, except at EOF. */
3021 if ((before
|| ! feof (ct
->c_fp
)) &&
3022 (cp
= content
+ strlen (content
)) > content
&&
3027 if (strlen (content
) > 1) {
3029 m
->mp_content_before
= content
;
3031 m
->mp_content_after
= content
;
3046 ct_type_str (int type
) {
3048 case CT_APPLICATION
:
3049 return "application";
3065 return "unknown_type";
3071 ct_subtype_str (int type
, int subtype
) {
3073 case CT_APPLICATION
:
3075 case APPLICATION_OCTETS
:
3077 case APPLICATION_POSTSCRIPT
:
3078 return "postscript";
3080 return "unknown_app_subtype";
3084 case MESSAGE_RFC822
:
3086 case MESSAGE_PARTIAL
:
3088 case MESSAGE_EXTERNAL
:
3091 return "unknown_msg_subtype";
3097 case MULTI_ALTERNATE
:
3098 return "alternative";
3101 case MULTI_PARALLEL
:
3106 return "unknown_multipart_subtype";
3117 return "unknown_text_subtype";
3120 return "unknown_type";
3126 ct_str_type (const char *type
) {
3127 struct str2init
*s2i
;
3129 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3130 if (! strcasecmp (type
, s2i
->si_key
)) {
3134 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3143 ct_str_subtype (int type
, const char *subtype
) {
3147 case CT_APPLICATION
:
3148 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3149 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3153 return kv
->kv_value
;
3155 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3156 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3160 return kv
->kv_value
;
3162 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3163 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3167 return kv
->kv_value
;
3169 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3170 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3174 return kv
->kv_value
;
3181 /* Find the content type and InitFunc for the CT. */
3182 const struct str2init
*
3183 get_ct_init (int type
) {
3184 const struct str2init
*sp
;
3186 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3187 if (type
== sp
->si_val
) {
3196 ce_str (int encoding
) {
3201 return "quoted-printable";
3217 /* Find the content type and InitFunc for the content encoding method. */
3218 const struct str2init
*
3219 get_ce_method (const char *method
) {
3220 struct str2init
*sp
;
3222 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3223 if (! strcasecmp (method
, sp
->si_key
)) {
3232 * Parse a series of MIME attributes (or parameters) given a header as
3235 * Arguments include:
3237 * filename - Name of input file (for error messages)
3238 * fieldname - Name of field being processed
3239 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3240 * Updated to point to end of attributes when finished.
3241 * param_head - Pointer to head of parameter list
3242 * param_tail - Pointer to tail of parameter list
3243 * commentp - Pointer to header comment pointer (may be NULL)
3245 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3246 * DONE to indicate a benign error (minor parsing error, but the program
3251 parse_header_attrs (const char *filename
, const char *fieldname
,
3252 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3255 char *cp
= *header_attrp
;
3261 struct sectlist
*next
;
3267 struct sectlist
*sechead
;
3268 struct parmlist
*next
;
3269 } *pp
, *pp2
, *phead
= NULL
;
3271 while (*cp
== ';') {
3272 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3273 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3276 while (isspace ((unsigned char) *cp
))
3280 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3285 if (! suppress_extraneous_trailing_semicolon_warning
) {
3286 inform("extraneous trailing ';' in message %s's %s: "
3287 "parameter list", filename
, fieldname
);
3292 /* down case the attribute name */
3293 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3294 *dp
= tolower ((unsigned char) *dp
);
3296 for (up
= dp
; isspace ((unsigned char) *dp
);)
3298 if (dp
== cp
|| *dp
!= '=') {
3299 inform("invalid parameter in message %s's %s: field\n"
3300 " parameter %s (error detected at offset %ld)",
3301 filename
, fieldname
, cp
, (long)(dp
- cp
));
3306 * To handle RFC 2231, we have to deal with the following extensions:
3308 * name*=encoded-value
3309 * name*<N>=part-N-of-a-parameter-value
3310 * name*<N>*=encoded-part-N-of-a-parameter-value
3313 * If there's a * right before the equal sign, it's encoded.
3314 * If there's a * and one or more digits, then it's section N.
3316 * Remember we can have one or the other, or both. cp points to
3317 * beginning of name, up points past the last character in the
3321 for (vp
= cp
; vp
< up
; vp
++) {
3322 if (*vp
== '*' && vp
< up
- 1) {
3326 if (*vp
== '*' && vp
== up
- 1) {
3328 } else if (partial
) {
3329 if (isdigit((unsigned char) *vp
))
3330 index
= *vp
- '0' + index
* 10;
3332 inform("invalid parameter index in message %s's %s: field"
3333 "\n (parameter %s)", filename
, fieldname
, cp
);
3342 * Break out the parameter name and value sections and allocate
3346 nameptr
= mh_xmalloc(len
+ 1);
3347 strncpy(nameptr
, cp
, len
);
3348 nameptr
[len
] = '\0';
3350 for (dp
++; isspace ((unsigned char) *dp
);)
3355 * Single quotes delimit the character set and language tag.
3356 * They are required on the first section (or a complete
3361 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3367 charset
= mh_xmalloc(len
+ 1);
3368 strncpy(charset
, dp
, len
);
3369 charset
[len
] = '\0';
3375 inform("missing charset in message %s's %s: field\n"
3376 " (parameter %s)", filename
, fieldname
, nameptr
);
3382 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3389 lang
= mh_xmalloc(len
+ 1);
3390 strncpy(lang
, dp
, len
);
3397 inform("missing language tag in message %s's %s: field\n"
3398 " (parameter %s)", filename
, fieldname
, nameptr
);
3408 * At this point vp should be pointing at the beginning
3409 * of the encoded value/section. Continue until we reach
3410 * the end or get whitespace. But first, calculate the
3411 * length so we can allocate the correct buffer size.
3414 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3416 if (*(vp
+ 1) == '\0' ||
3417 !isxdigit((unsigned char) *(vp
+ 1)) ||
3418 *(vp
+ 2) == '\0' ||
3419 !isxdigit((unsigned char) *(vp
+ 2))) {
3420 inform("invalid encoded sequence in message %s's %s: field\n"
3421 " (parameter %s)", filename
, fieldname
, nameptr
);
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: field\n"
3462 " (parameter %s)", filename
, fieldname
, nameptr
);
3482 for (cp
= dp
; istoken (*cp
); cp
++) {
3487 valptr
= mh_xmalloc(len
+ 1);
3491 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3499 strncpy(valptr
, cp
= dp
, len
);
3507 * If 'partial' is set, we don't allocate a parameter now. We
3508 * put it on the parameter linked list to be reassembled later.
3510 * "phead" points to a list of all parameters we need to reassemble.
3511 * Each parameter has a list of sections. We insert the sections in
3516 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3517 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3532 * Insert this into the section linked list
3540 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3541 sp
->next
= pp
->sechead
;
3544 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3545 if (sp2
->index
== sp
->index
) {
3546 inform("duplicate index (%d) in message %s's %s: field"
3547 "\n (parameter %s)", sp
->index
, filename
,
3548 fieldname
, nameptr
);
3551 if (sp2
->index
< sp
->index
&&
3552 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3553 sp
->next
= sp2
->next
;
3560 inform("Internal error: cannot insert partial param "
3561 "in message %s's %s: field\n (parameter %s)",
3562 filename
, fieldname
, nameptr
);
3568 * Save our charset and lang tags.
3571 if (index
== 0 && encoded
) {
3572 mh_xfree(pp
->charset
);
3573 pp
->charset
= charset
;
3578 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3579 pm
->pm_charset
= charset
;
3583 while (isspace ((unsigned char) *cp
))
3587 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3593 * Now that we're done, reassemble all of the partial parameters.
3596 for (pp
= phead
; pp
!= NULL
; ) {
3600 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3601 if (sp
->index
!= pindex
++) {
3602 inform("missing section %d for parameter in message "
3603 "%s's %s: field\n (parameter %s)", pindex
- 1,
3604 filename
, fieldname
, pp
->name
);
3610 p
= q
= mh_xmalloc(tlen
+ 1);
3611 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3612 memcpy(q
, sp
->value
, sp
->len
);
3622 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3623 pm
->pm_charset
= pp
->charset
;
3624 pm
->pm_lang
= pp
->lang
;
3635 * Return the charset for a particular content type.
3639 content_charset (CT ct
) {
3640 char *ret_charset
= NULL
;
3642 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3644 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3649 * Create a string based on a list of output parameters. Assume that this
3650 * parameter string will be appended to an existing header, so start out
3651 * with the separator (;). Perform RFC 2231 encoding when necessary.
3655 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3657 char *paramout
= NULL
;
3658 char line
[CPERLIN
* 2], *q
;
3659 int curlen
, index
, cont
, encode
, i
;
3660 size_t valoff
, numchars
;
3662 while (params
!= NULL
) {
3668 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3671 if (strlen(params
->pm_name
) > CPERLIN
) {
3672 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3677 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3680 * Loop until we get a parameter that fits within a line. We
3681 * assume new lines start with a tab, so check our overflow based
3691 * At this point we're definitely continuing the line, so
3692 * be sure to include the parameter name and section index.
3695 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3696 params
->pm_name
, index
);
3699 * Both of these functions do a NUL termination
3703 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3704 numchars
, valoff
, index
);
3706 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3716 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3721 * "line" starts with a ;\n\t, so that doesn't count against
3722 * the length. But add 8 since it starts with a tab; that's
3723 * how we end up with 5.
3726 initialwidth
= strlen(line
) + 5;
3729 * At this point the line should be built, so add it to our
3730 * current output buffer.
3733 paramout
= add(line
, paramout
);
3737 * If this won't fit on the line, start a new one. Save room in
3738 * case we need a semicolon on the end
3741 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3753 * At this point, we're either finishing a continued parameter, or
3754 * we're working on a new one.
3758 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3759 params
->pm_name
, index
);
3761 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3766 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3767 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3769 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3770 strlen(params
->pm_value
+ valoff
), valoff
);
3777 paramout
= add(line
, paramout
);
3778 initialwidth
+= strlen(line
);
3780 params
= params
->pm_next
;
3784 *offsetout
= initialwidth
;
3790 * Calculate the size of a parameter.
3794 * pm - The parameter being output
3795 * index - If continuing the parameter, the index of the section
3797 * valueoff - The current offset into the parameter value that we're
3798 * working on (previous sections have consumed valueoff bytes).
3799 * encode - Set if we should perform encoding on this parameter section
3800 * (given that we're consuming bytesfit bytes).
3801 * cont - Set if the remaining data in value will not fit on a single
3802 * line and will need to be continued.
3803 * bytesfit - The number of bytes that we can consume from the parameter
3804 * value and still fit on a completely new line. The
3805 * calculation assumes the new line starts with a tab,
3806 * includes the parameter name and any encoding, and fits
3807 * within CPERLIN bytes. Will always be at least 1.
3811 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3814 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3815 size_t len
= 0, fit
= 0;
3816 int fitlimit
= 0, eightbit
, maxfit
;
3821 * Add up the length. First, start with the parameter name.
3824 len
= strlen(pm
->pm_name
);
3827 * Scan the parameter value and see if we need to do encoding for this
3831 eightbit
= contains8bit(start
, NULL
);
3834 * Determine if we need to encode this section. Encoding is necessary if:
3836 * - There are any 8-bit characters at all and we're on the first
3838 * - There are 8-bit characters within N bytes of our section start.
3839 * N is calculated based on the number of bytes it would take to
3840 * reach CPERLIN. Specifically:
3841 * 8 (starting tab) +
3842 * strlen(param name) +
3843 * 4 ('* for section marker, '=', opening/closing '"')
3845 * is the number of bytes used by everything that isn't part of the
3846 * value. So that gets subtracted from CPERLIN.
3849 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3850 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3851 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3855 len
++; /* Add in equal sign */
3859 * We're using maxfit as a marker for how many characters we can
3860 * fit into the line. Bump it by two because we're not using quotes
3867 * If we don't have a charset or language tag in this parameter,
3871 if (! pm
->pm_charset
) {
3872 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3873 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3874 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3875 "local character set is US-ASCII", pm
->pm_name
);
3878 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3880 len
++; /* For the encoding marker */
3883 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3888 * We know we definitely need to include an index. maxfit already
3889 * includes the section marker.
3891 len
+= strlen(indexchar
);
3893 for (p
= start
; *p
!= '\0'; p
++) {
3894 if (isparamencode(*p
)) {
3902 * Just so there's no confusion: maxfit is counting OUTPUT
3903 * characters (post-encoding). fit is counting INPUT characters.
3905 if (! fitlimit
&& maxfit
>= 0)
3907 else if (! fitlimit
)
3912 * Calculate the string length, but add room for quoting \
3913 * and " if necessary. Also account for quotes at beginning
3916 for (p
= start
; *p
!= '\0'; p
++) {
3927 if (! fitlimit
&& maxfit
>= 0)
3929 else if (! fitlimit
)
3946 * Output an encoded parameter string.
3950 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3951 size_t valueoff
, int index
)
3953 size_t outlen
= 0, n
;
3954 char *endptr
= output
+ len
, *p
;
3957 * First, output the marker for an encoded string.
3965 * If the index is 0, output the character set and language tag.
3966 * If theses were NULL, they should have already been filled in
3971 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3975 if (output
> endptr
) {
3976 inform("Internal error: parameter buffer overflow");
3982 * Copy over the value, encoding if necessary
3985 p
= pm
->pm_value
+ valueoff
;
3986 while (valuelen
-- > 0) {
3987 if (isparamencode(*p
)) {
3988 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
3995 if (output
> endptr
) {
3996 inform("Internal error: parameter buffer overflow");
4007 * Output a "normal" parameter, without encoding. Be sure to escape
4008 * quotes and backslashes if necessary.
4012 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4016 char *endptr
= output
+ len
, *p
;
4022 p
= pm
->pm_value
+ valueoff
;
4024 while (valuelen
-- > 0) {
4035 if (output
> endptr
) {
4036 inform("Internal error: parameter buffer overflow");
4041 if (output
- 2 > endptr
) {
4042 inform("Internal error: parameter buffer overflow");
4053 * Add a parameter to the parameter linked list
4057 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4062 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4063 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4066 (*last
)->pm_next
= pm
;
4077 * Either replace a current parameter with a new value, or add the parameter
4078 * to the parameter linked list.
4082 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4086 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4087 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4089 * If nocopy is set, it's assumed that we own both name
4090 * and value. We don't need name, so we discard it now.
4095 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4100 return add_param(first
, last
, name
, value
, nocopy
);
4104 * Retrieve a parameter value from a parameter linked list. If the parameter
4105 * value needs converted to the local character set, do that now.
4109 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4111 while (first
!= NULL
) {
4112 if (strcasecmp(name
, first
->pm_name
) == 0) {
4114 return first
->pm_value
;
4115 return getcpy(get_param_value(first
, replace
));
4117 first
= first
->pm_next
;
4124 * Return a parameter value, converting to the local character set if
4128 char *get_param_value(PM pm
, char replace
)
4130 static char buffer
[4096]; /* I hope no parameters are larger */
4131 size_t bufsize
= sizeof(buffer
);
4136 ICONV_CONST
char *p
;
4137 #else /* HAVE_ICONV */
4139 #endif /* HAVE_ICONV */
4144 * If we don't have a character set indicated, it's assumed to be
4145 * US-ASCII. If it matches our character set, we don't need to convert
4149 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4150 strlen(pm
->pm_charset
))) {
4151 return pm
->pm_value
;
4155 * In this case, we need to convert. If we have iconv support, use
4156 * that. Otherwise, go through and simply replace every non-ASCII
4157 * character with the substitution character.
4162 bufsize
= sizeof(buffer
);
4163 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4165 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4166 if (cd
== (iconv_t
) -1) {
4170 inbytes
= strlen(pm
->pm_value
);
4174 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4175 if (errno
!= EILSEQ
) {
4180 * Reset shift state, substitute our character,
4181 * try to restart conversion.
4184 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4197 for (++p
, --inbytes
;
4198 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4217 #endif /* HAVE_ICONV */
4220 * Take everything non-ASCII and substitute the replacement character
4224 bufsize
= sizeof(buffer
);
4225 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4226 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))