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 /* There are two cases. The unusual one is when there is no
338 * blank line between the headers and the body. This is
339 * indicated by the name of the header starting with `:'.
341 * For both cases, normal first, `1' is the desired c_begin
342 * file position for the start of the body, and `2' is the
343 * file position when buf is returned.
345 * f o o : b a r \n \n b o d y \n bufsz = 6
347 * f o o : b a r \n b o d y \n bufsz = 4
350 * For the normal case, bufsz includes the
351 * header-terminating `\n', even though it is not in buf,
352 * but bufsz isn't affected when it's missing in the unusual
354 if (name
[0] == ':') {
355 ct
->c_begin
= ftell(in
) - bufsz
;
357 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
362 ct
->c_begin
= ftell (in
);
367 adios (NULL
, "message format error in component #%d", compnum
);
370 adios (NULL
, "getfld() returned %d", state
);
373 /* break out of the loop */
376 m_getfld_state_destroy (&gstate
);
379 * Read the content headers. We will parse the
380 * MIME related header fields into their various
381 * structures and set internal flags related to
382 * content type/subtype, etc.
385 hp
= ct
->c_first_hf
; /* start at first header field */
387 /* Get MIME-Version field */
388 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
393 vrsn
= add (hp
->value
, NULL
);
395 /* Now, cleanup this field */
398 while (isspace ((unsigned char) *cp
))
400 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
402 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
403 if (!isspace ((unsigned char) *dp
))
407 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
410 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
413 for (dp
= cp
; istoken (*dp
); dp
++)
417 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
420 inform("message %s has unknown value for %s: field (%s), continuing...",
421 ct
->c_file
, VRSN_FIELD
, cp
);
426 if (! suppress_multiple_mime_version_warning
)
427 inform("message %s has multiple %s: fields",
428 ct
->c_file
, VRSN_FIELD
);
432 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
433 /* Get Content-Type field */
434 struct str2init
*s2i
;
435 CI ci
= &ct
->c_ctinfo
;
437 /* Check if we've already seen a Content-Type header */
439 inform("message %s has multiple %s: fields",
440 ct
->c_file
, TYPE_FIELD
);
444 /* Parse the Content-Type field */
445 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
449 * Set the Init function and the internal
450 * flag for this content type.
452 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
453 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
455 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
457 ct
->c_type
= s2i
->si_val
;
458 ct
->c_ctinitfnx
= s2i
->si_init
;
460 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
461 /* Get Content-Transfer-Encoding field */
463 struct str2init
*s2i
;
466 * Check if we've already seen the
467 * Content-Transfer-Encoding field
470 inform("message %s has multiple %s: fields",
471 ct
->c_file
, ENCODING_FIELD
);
475 /* get copy of this field */
476 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
478 while (isspace ((unsigned char) *cp
))
480 for (dp
= cp
; istoken (*dp
); dp
++)
486 * Find the internal flag and Init function
487 * for this transfer encoding.
489 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
490 if (!strcasecmp (cp
, s2i
->si_key
))
492 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
495 ct
->c_encoding
= s2i
->si_val
;
497 /* Call the Init function for this encoding */
498 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
501 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
502 /* Get Content-MD5 field */
508 if (ct
->c_digested
) {
509 inform("message %s has multiple %s: fields",
510 ct
->c_file
, MD5_FIELD
);
514 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
516 while (isspace ((unsigned char) *cp
))
518 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
520 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
521 if (!isspace ((unsigned char) *dp
))
525 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
528 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
533 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
541 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
542 /* Get Content-ID field */
543 ct
->c_id
= add (hp
->value
, ct
->c_id
);
545 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
546 /* Get Content-Description field */
547 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
549 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
550 /* Get Content-Disposition field */
551 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
556 hp
= hp
->next
; /* next header field */
560 * Check if we saw a Content-Type field.
561 * If not, then assign a default value for
562 * it, and the Init function.
566 * If we are inside a multipart/digest message,
567 * so default type is message/rfc822
570 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
572 ct
->c_type
= CT_MESSAGE
;
573 ct
->c_ctinitfnx
= InitMessage
;
576 * Else default type is text/plain
578 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
580 ct
->c_type
= CT_TEXT
;
581 ct
->c_ctinitfnx
= InitText
;
585 /* Use default Transfer-Encoding, if necessary */
587 ct
->c_encoding
= CE_7BIT
;
600 * small routine to add header field to list
604 add_header (CT ct
, char *name
, char *value
)
608 /* allocate header field structure */
611 /* link data into header structure */
616 /* link header structure into the list */
617 if (ct
->c_first_hf
== NULL
) {
618 ct
->c_first_hf
= hp
; /* this is the first */
621 ct
->c_last_hf
->next
= hp
; /* add it to the end */
630 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
631 * directives. Fills in the information of the CTinfo structure.
634 get_ctinfo (char *cp
, CT ct
, int magic
)
643 /* store copy of Content-Type line */
644 cp
= ct
->c_ctline
= add (cp
, NULL
);
646 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
649 /* change newlines to spaces */
650 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
653 /* trim trailing spaces */
654 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
655 if (!isspace ((unsigned char) *dp
))
660 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
662 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
663 &ci
->ci_comment
) == NOTOK
)
666 for (dp
= cp
; istoken (*dp
); dp
++)
670 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
675 inform("invalid %s: field in message %s (empty type)",
676 TYPE_FIELD
, ct
->c_file
);
679 to_lower(ci
->ci_type
);
681 while (isspace ((unsigned char) *cp
))
684 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
685 &ci
->ci_comment
) == NOTOK
)
690 ci
->ci_subtype
= mh_xstrdup("");
695 while (isspace ((unsigned char) *cp
))
698 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
699 &ci
->ci_comment
) == NOTOK
)
702 for (dp
= cp
; istoken (*dp
); dp
++)
706 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
710 if (!*ci
->ci_subtype
) {
711 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
712 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
715 to_lower(ci
->ci_subtype
);
718 while (isspace ((unsigned char) *cp
))
721 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
722 &ci
->ci_comment
) == NOTOK
)
725 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
726 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
727 &ci
->ci_comment
)) != OK
) {
728 return status
== NOTOK
? NOTOK
: OK
;
732 * Get any <Content-Id> given in buffer
734 if (magic
&& *cp
== '<') {
737 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
738 inform("invalid ID in message %s", ct
->c_file
);
744 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
750 while (isspace ((unsigned char) *cp
))
755 * Get any [Content-Description] given in buffer.
757 if (magic
&& *cp
== '[') {
759 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
763 inform("invalid description in message %s", ct
->c_file
);
771 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
777 while (isspace ((unsigned char) *cp
))
782 * Get any {Content-Disposition} given in buffer.
784 if (magic
&& *cp
== '{') {
786 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
790 inform("invalid disposition in message %s", ct
->c_file
);
798 if (get_dispo(cp
, ct
, 1) != OK
)
804 while (isspace ((unsigned char) *cp
))
809 * Get any extension directives (right now just the content transfer
810 * encoding, but maybe others) that we care about.
813 if (magic
&& *cp
== '*') {
815 * See if it's a CTE we match on
820 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
824 inform("invalid null transfer encoding specification");
831 ct
->c_reqencoding
= CE_UNKNOWN
;
833 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
834 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
835 ct
->c_reqencoding
= kv
->kv_value
;
840 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
841 inform("invalid CTE specification: \"%s\"", dp
);
845 while (isspace ((unsigned char) *cp
))
850 * Check if anything is left over
854 ci
->ci_magic
= mh_xstrdup(cp
);
856 /* If there is a Content-Disposition header and it doesn't
857 have a *filename=, extract it from the magic contents.
858 The r1bindex call skips any leading directory
860 if (ct
->c_dispo_type
&&
861 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
862 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
863 r1bindex(ci
->ci_magic
, '/'), 0);
867 inform("extraneous information in message %s's %s: field\n%*s(%s)",
868 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
876 * Parse out a Content-Disposition header. A lot of this is cribbed from
880 get_dispo (char *cp
, CT ct
, int buildflag
)
882 char *dp
, *dispoheader
;
887 * Save the whole copy of the Content-Disposition header, unless we're
888 * processing a mhbuild directive. A NULL c_dispo will be a flag to
889 * mhbuild that the disposition header needs to be generated at that
893 dispoheader
= cp
= add(cp
, NULL
);
895 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
898 /* change newlines to spaces */
899 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
902 /* trim trailing spaces */
903 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
904 if (!isspace ((unsigned char) *dp
))
909 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
911 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
917 for (dp
= cp
; istoken (*dp
); dp
++)
921 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
925 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
928 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
929 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
931 if (status
== NOTOK
) {
936 inform("extraneous information in message %s's %s: field\n%*s(%s)",
937 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
943 ct
->c_dispo
= dispoheader
;
950 get_comment (const char *filename
, const char *fieldname
, char **ap
,
955 char c
, buffer
[BUFSIZ
], *dp
;
965 inform("invalid comment in message %s's %s: field",
966 filename
, fieldname
);
971 if ((c
= *cp
++) == '\0')
994 if ((dp
= *commentp
)) {
995 *commentp
= concat (dp
, " ", buffer
, NULL
);
998 *commentp
= mh_xstrdup(buffer
);
1002 while (isspace ((unsigned char) *cp
))
1013 * Handles content types audio, image, and video.
1014 * There's not much to do right here.
1022 return OK
; /* not much to do here */
1033 char buffer
[BUFSIZ
];
1038 CI ci
= &ct
->c_ctinfo
;
1040 /* check for missing subtype */
1041 if (!*ci
->ci_subtype
)
1042 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1045 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1047 /* allocate text character set structure */
1049 ct
->c_ctparams
= (void *) t
;
1051 /* scan for charset parameter */
1052 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1053 if (!strcasecmp (pm
->pm_name
, "charset"))
1056 /* check if content specified a character set */
1058 chset
= pm
->pm_value
;
1059 t
->tx_charset
= CHARSET_SPECIFIED
;
1061 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1065 * If we can not handle character set natively,
1066 * then check profile for string to modify the
1067 * terminal or display method.
1069 * termproc is for mhshow, though mhlist -debug prints it, too.
1071 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1072 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1073 if ((cp
= context_find (buffer
)))
1074 ct
->c_termproc
= mh_xstrdup(cp
);
1086 InitMultiPart (CT ct
)
1096 struct multipart
*m
;
1097 struct part
*part
, **next
;
1098 CI ci
= &ct
->c_ctinfo
;
1103 * The encoding for multipart messages must be either
1104 * 7bit, 8bit, or binary (per RFC 2045).
1106 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1107 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1108 /* Copy the Content-Transfer-Encoding header field body so we can
1109 remove any trailing whitespace and leading blanks from it. */
1110 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1112 bp
= cte
+ strlen (cte
) - 1;
1113 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1114 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1116 inform("\"%s/%s\" type in message %s must be encoded in\n"
1117 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1118 "mhfixmsg -fixcte can fix it, or\n"
1119 "manually edit the file and change the \"%s\"\n"
1120 "Content-Transfer-Encoding to one of those. For now, continuing...",
1121 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1128 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1131 * Check for "boundary" parameter, which is
1132 * required for multipart messages.
1135 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1136 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1142 /* complain if boundary parameter is missing */
1144 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1145 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1149 /* allocate primary structure for multipart info */
1151 ct
->c_ctparams
= (void *) m
;
1153 /* check if boundary parameter contains only whitespace characters */
1154 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1157 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1158 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1162 /* remove trailing whitespace from boundary parameter */
1163 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1164 if (!isspace ((unsigned char) *dp
))
1168 /* record boundary separators */
1169 m
->mp_start
= concat (bp
, "\n", NULL
);
1170 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1172 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1173 advise (ct
->c_file
, "unable to open for reading");
1177 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1179 next
= &m
->mp_parts
;
1183 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1188 if (bufp
[0] != '-' || bufp
[1] != '-')
1191 if (strcmp (bufp
+ 2, m
->mp_start
))
1196 next
= &part
->mp_next
;
1198 if (!(p
= get_content (fp
, ct
->c_file
,
1199 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1207 fseek (fp
, pos
, SEEK_SET
);
1210 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1214 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1215 if (p
->c_end
< p
->c_begin
)
1216 p
->c_begin
= p
->c_end
;
1221 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1226 if (! suppress_bogus_mp_content_warning
) {
1227 inform("bogus multipart content in message %s", ct
->c_file
);
1229 bogus_mp_content
= 1;
1231 if (!inout
&& part
) {
1233 p
->c_end
= ct
->c_end
;
1235 if (p
->c_begin
>= p
->c_end
) {
1236 for (next
= &m
->mp_parts
; *next
!= part
;
1237 next
= &((*next
)->mp_next
))
1246 /* reverse the order of the parts for multipart/alternative */
1247 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1253 * label all subparts with part number, and
1254 * then initialize the content of the subpart.
1259 char partnam
[BUFSIZ
];
1262 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1263 pp
= partnam
+ strlen (partnam
);
1268 for (part
= m
->mp_parts
, partnum
= 1; part
;
1269 part
= part
->mp_next
, partnum
++) {
1272 sprintf (pp
, "%d", partnum
);
1273 p
->c_partno
= mh_xstrdup(partnam
);
1275 /* initialize the content of the subparts */
1276 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1285 get_leftover_mp_content (ct
, 1);
1286 get_leftover_mp_content (ct
, 0);
1296 * reverse the order of the parts of a multipart/alternative,
1297 * presumably to put the "most favored" alternative first, for
1298 * ease of choosing/displaying it later on. from a mail message on
1299 * nmh-workers, from kenh:
1300 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1301 * see code in mhn that did the same thing... According to the RCS
1302 * logs, that code was around from the initial checkin of mhn.c by
1303 * John Romine in 1992, which is as far back as we have."
1306 reverse_parts (CT ct
)
1308 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1312 /* Reverse the order of its parts by walking the mp_parts list
1313 and pushing each node to the front. */
1314 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1315 next
= part
->mp_next
;
1316 part
->mp_next
= m
->mp_parts
;
1322 move_preferred_part (CT ct
, char *type
, char *subtype
)
1324 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1325 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1329 /* move the matching part(s) to the head of the list: walk the
1330 * list of parts, move matching parts to a new list (maintaining
1331 * their order), and finally, concatenate the old list onto the
1338 head
->mp_next
= m
->mp_parts
;
1339 nhead
->mp_next
= NULL
;
1343 part
= head
->mp_next
;
1344 while (part
!= NULL
) {
1345 ci
= &part
->mp_part
->c_ctinfo
;
1346 if (!strcasecmp(ci
->ci_type
, type
) &&
1347 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1348 prev
->mp_next
= part
->mp_next
;
1349 part
->mp_next
= NULL
;
1350 ntail
->mp_next
= part
;
1352 part
= prev
->mp_next
;
1355 part
= prev
->mp_next
;
1358 ntail
->mp_next
= head
->mp_next
;
1359 m
->mp_parts
= nhead
->mp_next
;
1364 * move parts that match the user's preferences (-prefer) to the head
1365 * of the line. process preferences in reverse so first one given
1366 * ends up first in line
1372 for (i
= npreferred
-1; i
>= 0; i
--)
1373 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1378 /* parse_mime() arranges alternates in reverse (priority) order. This
1379 function can be used to reverse them back. This will put, for
1380 example, a text/plain part before a text/html part in a
1381 multipart/alternative part, for example, where it belongs. */
1383 reverse_alternative_parts (CT ct
) {
1384 if (ct
->c_type
== CT_MULTIPART
) {
1385 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1388 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1392 /* And call recursively on each part of a multipart. */
1393 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1394 reverse_alternative_parts (part
->mp_part
);
1407 CI ci
= &ct
->c_ctinfo
;
1409 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1410 inform("\"%s/%s\" type in message %s should be encoded in "
1411 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1416 /* check for missing subtype */
1417 if (!*ci
->ci_subtype
)
1418 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1421 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1423 switch (ct
->c_subtype
) {
1424 case MESSAGE_RFC822
:
1427 case MESSAGE_PARTIAL
:
1433 ct
->c_ctparams
= (void *) p
;
1435 /* scan for parameters "id", "number", and "total" */
1436 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1437 if (!strcasecmp (pm
->pm_name
, "id")) {
1438 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1441 if (!strcasecmp (pm
->pm_name
, "number")) {
1442 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1443 || p
->pm_partno
< 1) {
1445 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1446 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1447 ct
->c_file
, TYPE_FIELD
);
1452 if (!strcasecmp (pm
->pm_name
, "total")) {
1453 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1462 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1463 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1464 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1470 case MESSAGE_EXTERNAL
:
1478 ct
->c_ctparams
= (void *) e
;
1481 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1482 advise (ct
->c_file
, "unable to open for reading");
1486 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1488 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1496 p
->c_ceopenfnx
= NULL
;
1497 if ((exresult
= params_external (ct
, 0)) != NOTOK
1498 && p
->c_ceopenfnx
== openMail
) {
1502 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1504 content_error (NULL
, ct
,
1505 "empty body for access-type=mail-server");
1509 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1510 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1512 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1514 adios ("failed", "fread");
1517 adios (NULL
, "unexpected EOF from fread");
1520 bp
+= cc
, size
-= cc
;
1527 p
->c_end
= p
->c_begin
;
1532 if (exresult
== NOTOK
)
1534 if (e
->eb_flags
== NOTOK
)
1537 switch (p
->c_type
) {
1542 if (p
->c_subtype
!= MESSAGE_RFC822
)
1546 e
->eb_partno
= ct
->c_partno
;
1548 (*p
->c_ctinitfnx
) (p
);
1563 params_external (CT ct
, int composing
)
1566 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1567 CI ci
= &ct
->c_ctinfo
;
1569 ct
->c_ceopenfnx
= NULL
;
1570 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1571 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1572 struct str2init
*s2i
;
1573 CT p
= e
->eb_content
;
1575 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1576 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1579 e
->eb_access
= pm
->pm_value
;
1580 e
->eb_flags
= NOTOK
;
1581 p
->c_encoding
= CE_EXTERNAL
;
1584 e
->eb_access
= s2i
->si_key
;
1585 e
->eb_flags
= s2i
->si_val
;
1586 p
->c_encoding
= CE_EXTERNAL
;
1588 /* Call the Init function for this external type */
1589 if ((*s2i
->si_init
)(p
) == NOTOK
)
1593 if (!strcasecmp (pm
->pm_name
, "name")) {
1594 e
->eb_name
= pm
->pm_value
;
1597 if (!strcasecmp (pm
->pm_name
, "permission")) {
1598 e
->eb_permission
= pm
->pm_value
;
1601 if (!strcasecmp (pm
->pm_name
, "site")) {
1602 e
->eb_site
= pm
->pm_value
;
1605 if (!strcasecmp (pm
->pm_name
, "directory")) {
1606 e
->eb_dir
= pm
->pm_value
;
1609 if (!strcasecmp (pm
->pm_name
, "mode")) {
1610 e
->eb_mode
= pm
->pm_value
;
1613 if (!strcasecmp (pm
->pm_name
, "size")) {
1614 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1617 if (!strcasecmp (pm
->pm_name
, "server")) {
1618 e
->eb_server
= pm
->pm_value
;
1621 if (!strcasecmp (pm
->pm_name
, "subject")) {
1622 e
->eb_subject
= pm
->pm_value
;
1625 if (!strcasecmp (pm
->pm_name
, "url")) {
1627 * According to RFC 2017, we have to remove all whitespace from
1631 char *u
, *p
= pm
->pm_value
;
1632 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1634 for (; *p
!= '\0'; p
++) {
1635 if (! isspace((unsigned char) *p
))
1642 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1643 e
->eb_body
= getcpy (pm
->pm_value
);
1648 if (!e
->eb_access
) {
1649 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1650 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1663 InitApplication (CT ct
)
1665 CI ci
= &ct
->c_ctinfo
;
1668 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1675 * TRANSFER ENCODINGS
1679 init_encoding (CT ct
, OpenCEFunc openfnx
)
1681 ct
->c_ceopenfnx
= openfnx
;
1682 ct
->c_ceclosefnx
= close_encoding
;
1683 ct
->c_cesizefnx
= size_encoding
;
1690 close_encoding (CT ct
)
1692 CE ce
= &ct
->c_cefile
;
1701 static unsigned long
1702 size_encoding (CT ct
)
1707 CE ce
= &ct
->c_cefile
;
1710 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1711 return (long) st
.st_size
;
1714 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1715 return (long) st
.st_size
;
1719 if (ct
->c_encoding
== CE_EXTERNAL
)
1720 return (ct
->c_end
- ct
->c_begin
);
1723 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1724 return (ct
->c_end
- ct
->c_begin
);
1726 if (fstat (fd
, &st
) != NOTOK
)
1727 size
= (long) st
.st_size
;
1731 (*ct
->c_ceclosefnx
) (ct
);
1743 return init_encoding (ct
, openBase64
);
1748 openBase64 (CT ct
, char **file
)
1751 int fd
, own_ct_fp
= 0;
1752 char *cp
, *buffer
= NULL
;
1753 /* sbeck -- handle suffixes */
1755 CE ce
= &ct
->c_cefile
;
1756 unsigned char *decoded
;
1758 unsigned char digest
[16];
1761 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1766 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1767 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1773 if (*file
== NULL
) {
1776 ce
->ce_file
= mh_xstrdup(*file
);
1780 /* sbeck@cise.ufl.edu -- handle suffixes */
1782 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1783 if (ce
->ce_unlink
) {
1784 /* Create temporary file with filename extension. */
1785 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1786 adios(NULL
, "unable to create temporary file in %s",
1790 ce
->ce_file
= add (cp
, ce
->ce_file
);
1792 } else if (*file
== NULL
) {
1794 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1795 adios(NULL
, "unable to create temporary file in %s",
1798 ce
->ce_file
= mh_xstrdup(tempfile
);
1801 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1802 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1806 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1807 adios (NULL
, "internal error(1)");
1809 buffer
= mh_xmalloc (len
+ 1);
1812 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1813 content_error (ct
->c_file
, ct
, "unable to open for reading");
1819 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1822 switch (cc
= read (fd
, cp
, len
)) {
1824 content_error (ct
->c_file
, ct
, "error reading from");
1828 content_error (NULL
, ct
, "premature eof");
1839 /* decodeBase64() requires null-terminated input. */
1842 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1843 ct
->c_digested
? digest
: NULL
) == OK
) {
1845 unsigned char *decoded_p
= decoded
;
1846 for (i
= 0; i
< decoded_len
; ++i
) {
1847 putc (*decoded_p
++, ce
->ce_fp
);
1850 if (ferror (ce
->ce_fp
)) {
1851 content_error (ce
->ce_file
, ct
, "error writing to");
1855 if (ct
->c_digested
) {
1856 if (memcmp(digest
, ct
->c_digest
,
1858 content_error (NULL
, ct
,
1859 "content integrity suspect (digest mismatch) -- continuing");
1862 fprintf (stderr
, "content integrity confirmed\n");
1870 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1872 if (fflush (ce
->ce_fp
)) {
1873 content_error (ce
->ce_file
, ct
, "error writing to");
1877 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1880 *file
= ce
->ce_file
;
1886 return fileno (ce
->ce_fp
);
1893 free_encoding (ct
, 0);
1903 static char hex2nib
[0x80] = {
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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1910 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1911 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1912 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1913 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1914 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1915 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1916 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1917 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1918 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1919 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1926 return init_encoding (ct
, openQuoted
);
1931 openQuoted (CT ct
, char **file
)
1933 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1939 CE ce
= &ct
->c_cefile
;
1940 /* sbeck -- handle suffixes */
1945 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1950 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1951 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1957 if (*file
== NULL
) {
1960 ce
->ce_file
= mh_xstrdup(*file
);
1964 /* sbeck@cise.ufl.edu -- handle suffixes */
1966 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1967 if (ce
->ce_unlink
) {
1968 /* Create temporary file with filename extension. */
1969 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1970 adios(NULL
, "unable to create temporary file in %s",
1974 ce
->ce_file
= add (cp
, ce
->ce_file
);
1976 } else if (*file
== NULL
) {
1978 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1979 adios(NULL
, "unable to create temporary file in %s",
1982 ce
->ce_file
= mh_xstrdup(tempfile
);
1985 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1986 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1990 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1991 adios (NULL
, "internal error(2)");
1994 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1995 content_error (ct
->c_file
, ct
, "unable to open for reading");
2001 if ((digested
= ct
->c_digested
))
2002 MD5Init (&mdContext
);
2009 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2011 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2012 content_error (NULL
, ct
, "premature eof");
2016 if ((cc
= gotlen
) > len
)
2020 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2021 if (!isspace ((unsigned char) *ep
))
2026 for (; cp
< ep
; cp
++) {
2028 /* in an escape sequence */
2030 /* at byte 1 of an escape sequence */
2031 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2032 /* next is byte 2 */
2035 /* at byte 2 of an escape sequence */
2037 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2038 putc (mask
, ce
->ce_fp
);
2040 MD5Update (&mdContext
, &mask
, 1);
2041 if (ferror (ce
->ce_fp
)) {
2042 content_error (ce
->ce_file
, ct
, "error writing to");
2045 /* finished escape sequence; next may be literal or a new
2046 * escape sequence */
2049 /* on to next byte */
2053 /* not in an escape sequence */
2055 /* starting an escape sequence, or invalid '='? */
2056 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2057 /* "=\n" soft line break, eat the \n */
2061 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2062 /* We don't have 2 bytes left, so this is an invalid
2063 * escape sequence; just show the raw bytes (below). */
2064 } else if (isxdigit ((unsigned char) cp
[1]) &&
2065 isxdigit ((unsigned char) cp
[2])) {
2066 /* Next 2 bytes are hex digits, making this a valid escape
2067 * sequence; let's decode it (above). */
2071 /* One or both of the next 2 is out of range, making this
2072 * an invalid escape sequence; just show the raw bytes
2076 /* Just show the raw byte. */
2077 putc (*cp
, ce
->ce_fp
);
2080 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2082 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2085 if (ferror (ce
->ce_fp
)) {
2086 content_error (ce
->ce_file
, ct
, "error writing to");
2092 content_error (NULL
, ct
,
2093 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2097 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2099 if (fflush (ce
->ce_fp
)) {
2100 content_error (ce
->ce_file
, ct
, "error writing to");
2105 unsigned char digest
[16];
2107 MD5Final (digest
, &mdContext
);
2108 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2110 content_error (NULL
, ct
,
2111 "content integrity suspect (digest mismatch) -- continuing");
2114 fprintf (stderr
, "content integrity confirmed\n");
2117 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2120 *file
= ce
->ce_file
;
2126 return fileno (ce
->ce_fp
);
2129 free_encoding (ct
, 0);
2146 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2149 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2155 open7Bit (CT ct
, char **file
)
2157 int cc
, fd
, len
, own_ct_fp
= 0;
2158 char buffer
[BUFSIZ
];
2159 /* sbeck -- handle suffixes */
2162 CE ce
= &ct
->c_cefile
;
2165 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2170 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2171 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2177 if (*file
== NULL
) {
2180 ce
->ce_file
= mh_xstrdup(*file
);
2184 /* sbeck@cise.ufl.edu -- handle suffixes */
2186 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2187 if (ce
->ce_unlink
) {
2188 /* Create temporary file with filename extension. */
2189 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2190 adios(NULL
, "unable to create temporary file in %s",
2194 ce
->ce_file
= add (cp
, ce
->ce_file
);
2196 } else if (*file
== NULL
) {
2198 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2199 adios(NULL
, "unable to create temporary file in %s",
2202 ce
->ce_file
= mh_xstrdup(tempfile
);
2205 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2206 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2210 if (ct
->c_type
== CT_MULTIPART
) {
2211 CI ci
= &ct
->c_ctinfo
;
2215 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2216 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2217 + 1 + strlen (ci
->ci_subtype
);
2218 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2221 fputs (buffer
, ce
->ce_fp
);
2225 if (ci
->ci_comment
) {
2226 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2227 fputs ("\n\t", ce
->ce_fp
);
2231 putc (' ', ce
->ce_fp
);
2234 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2237 fprintf (ce
->ce_fp
, "\n");
2239 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2241 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2243 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2244 fprintf (ce
->ce_fp
, "\n");
2247 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2248 adios (NULL
, "internal error(3)");
2251 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2252 content_error (ct
->c_file
, ct
, "unable to open for reading");
2258 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2260 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2262 content_error (ct
->c_file
, ct
, "error reading from");
2266 content_error (NULL
, ct
, "premature eof");
2274 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2275 advise ("open7Bit", "fwrite");
2277 if (ferror (ce
->ce_fp
)) {
2278 content_error (ce
->ce_file
, ct
, "error writing to");
2283 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2285 if (fflush (ce
->ce_fp
)) {
2286 content_error (ce
->ce_file
, ct
, "error writing to");
2290 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2293 *file
= ce
->ce_file
;
2298 return fileno (ce
->ce_fp
);
2301 free_encoding (ct
, 0);
2315 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2317 char cachefile
[BUFSIZ
];
2320 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2325 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2326 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2332 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2333 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2334 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2335 ce
->ce_file
= mh_xstrdup(cachefile
);
2339 admonish (cachefile
, "unable to fopen for reading");
2342 *fd
= fileno (ce
->ce_fp
);
2346 *file
= ce
->ce_file
;
2347 *fd
= fileno (ce
->ce_fp
);
2358 return init_encoding (ct
, openFile
);
2363 openFile (CT ct
, char **file
)
2366 char cachefile
[BUFSIZ
];
2367 struct exbody
*e
= ct
->c_ctexbody
;
2368 CE ce
= &ct
->c_cefile
;
2370 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2382 content_error (NULL
, ct
, "missing name parameter");
2386 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2389 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2390 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2394 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2395 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2396 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2400 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2401 if ((fp
= fopen (cachefile
, "w"))) {
2403 char buffer
[BUFSIZ
];
2404 FILE *gp
= ce
->ce_fp
;
2406 fseek (gp
, 0L, SEEK_SET
);
2408 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2410 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2411 advise ("openFile", "fwrite");
2416 admonish (ce
->ce_file
, "error reading");
2417 (void) m_unlink (cachefile
);
2421 admonish (cachefile
, "error writing");
2422 (void) m_unlink (cachefile
);
2429 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2430 *file
= ce
->ce_file
;
2431 return fileno (ce
->ce_fp
);
2441 return init_encoding (ct
, openFTP
);
2446 openFTP (CT ct
, char **file
)
2448 int cachetype
, caching
, fd
;
2450 char *bp
, *ftp
, *user
, *pass
;
2451 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2453 CE ce
= &ct
->c_cefile
;
2454 static char *username
= NULL
;
2455 static char *password
= NULL
;
2459 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2465 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2476 if (!e
->eb_name
|| !e
->eb_site
) {
2477 content_error (NULL
, ct
, "missing %s parameter",
2478 e
->eb_name
? "site": "name");
2482 /* Get the buffer ready to go */
2484 buflen
= sizeof(buffer
);
2487 * Construct the query message for user
2489 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2495 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2501 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2502 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2507 if (e
->eb_size
> 0) {
2508 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2513 snprintf (bp
, buflen
, "? ");
2516 * Now, check the answer
2518 if (!read_yes_or_no_if_tty (buffer
))
2523 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2527 ruserpass (e
->eb_site
, &username
, &password
, 0);
2532 ce
->ce_unlink
= (*file
== NULL
);
2534 cachefile
[0] = '\0';
2535 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2536 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2537 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2538 if (*file
== NULL
) {
2545 ce
->ce_file
= mh_xstrdup(*file
);
2547 ce
->ce_file
= mh_xstrdup(cachefile
);
2550 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2551 adios(NULL
, "unable to create temporary file in %s",
2554 ce
->ce_file
= mh_xstrdup(tempfile
);
2557 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2558 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2563 int child_id
, i
, vecp
;
2567 vec
[vecp
++] = r1bindex (ftp
, '/');
2568 vec
[vecp
++] = e
->eb_site
;
2571 vec
[vecp
++] = e
->eb_dir
;
2572 vec
[vecp
++] = e
->eb_name
;
2573 vec
[vecp
++] = ce
->ce_file
,
2574 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2575 ? "ascii" : "binary";
2580 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2584 adios ("fork", "unable to");
2588 close (fileno (ce
->ce_fp
));
2590 fprintf (stderr
, "unable to exec ");
2596 if (pidXwait (child_id
, NULL
)) {
2597 username
= password
= NULL
;
2607 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2612 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2613 if ((fp
= fopen (cachefile
, "w"))) {
2615 FILE *gp
= ce
->ce_fp
;
2617 fseek (gp
, 0L, SEEK_SET
);
2619 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2621 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2622 advise ("openFTP", "fwrite");
2627 admonish (ce
->ce_file
, "error reading");
2628 (void) m_unlink (cachefile
);
2632 admonish (cachefile
, "error writing");
2633 (void) m_unlink (cachefile
);
2641 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2642 *file
= ce
->ce_file
;
2643 return fileno (ce
->ce_fp
);
2654 return init_encoding (ct
, openMail
);
2659 openMail (CT ct
, char **file
)
2661 int child_id
, fd
, i
, vecp
;
2663 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2664 struct exbody
*e
= ct
->c_ctexbody
;
2665 CE ce
= &ct
->c_cefile
;
2667 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2678 if (!e
->eb_server
) {
2679 content_error (NULL
, ct
, "missing server parameter");
2683 /* Get buffer ready to go */
2685 buflen
= sizeof(buffer
);
2687 /* Now, construct query message */
2688 snprintf (bp
, buflen
, "Retrieve content");
2694 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2700 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2702 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2704 /* Now, check answer */
2705 if (!read_yes_or_no_if_tty (buffer
))
2709 vec
[vecp
++] = r1bindex (mailproc
, '/');
2710 vec
[vecp
++] = e
->eb_server
;
2711 vec
[vecp
++] = "-subject";
2712 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2713 vec
[vecp
++] = "-body";
2714 vec
[vecp
++] = e
->eb_body
;
2717 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2721 advise ("fork", "unable to");
2725 execvp (mailproc
, vec
);
2726 fprintf (stderr
, "unable to exec ");
2732 if (pidXwait (child_id
, NULL
) == OK
)
2733 inform("request sent");
2737 if (*file
== NULL
) {
2739 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2740 adios(NULL
, "unable to create temporary file in %s",
2743 ce
->ce_file
= mh_xstrdup(tempfile
);
2746 ce
->ce_file
= mh_xstrdup(*file
);
2750 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2751 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2755 /* showproc is for mhshow and mhstore, though mhlist -debug
2756 * prints it, too. */
2757 mh_xfree(ct
->c_showproc
);
2758 ct
->c_showproc
= mh_xstrdup("true");
2760 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2761 *file
= ce
->ce_file
;
2762 return fileno (ce
->ce_fp
);
2773 return init_encoding (ct
, openURL
);
2778 openURL (CT ct
, char **file
)
2780 struct exbody
*e
= ct
->c_ctexbody
;
2781 CE ce
= &ct
->c_cefile
;
2782 char *urlprog
, *program
;
2783 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2784 int fd
, caching
, cachetype
;
2785 struct msgs_array args
= { 0, 0, NULL
};
2788 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2792 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2796 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2808 content_error(NULL
, ct
, "missing url parameter");
2812 ce
->ce_unlink
= (*file
== NULL
);
2814 cachefile
[0] = '\0';
2816 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2817 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2818 if (*file
== NULL
) {
2825 ce
->ce_file
= mh_xstrdup(*file
);
2827 ce
->ce_file
= mh_xstrdup(cachefile
);
2830 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2831 adios(NULL
, "unable to create temporary file in %s",
2834 ce
->ce_file
= mh_xstrdup(tempfile
);
2837 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2838 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2842 switch (child_id
= fork()) {
2844 adios ("fork", "unable to");
2848 argsplit_msgarg(&args
, urlprog
, &program
);
2849 app_msgarg(&args
, e
->eb_url
);
2850 app_msgarg(&args
, NULL
);
2851 dup2(fileno(ce
->ce_fp
), 1);
2852 close(fileno(ce
->ce_fp
));
2853 execvp(program
, args
.msgs
);
2854 fprintf(stderr
, "Unable to exec ");
2860 if (pidXwait(child_id
, NULL
)) {
2868 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2873 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2874 if ((fp
= fopen(cachefile
, "w"))) {
2876 FILE *gp
= ce
->ce_fp
;
2878 fseeko(gp
, 0, SEEK_SET
);
2880 while ((cc
= fread(buffer
, sizeof(*buffer
),
2881 sizeof(buffer
), gp
)) > 0)
2882 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2883 advise ("openURL", "fwrite");
2889 admonish(ce
->ce_file
, "error reading");
2890 (void) m_unlink (cachefile
);
2897 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2898 *file
= ce
->ce_file
;
2904 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2905 * has to be base64 decoded.
2908 readDigest (CT ct
, char *cp
)
2910 unsigned char *digest
;
2913 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2914 const size_t maxlen
= sizeof ct
->c_digest
;
2916 if (strlen ((char *) digest
) <= maxlen
) {
2917 memcpy (ct
->c_digest
, digest
, maxlen
);
2922 fprintf (stderr
, "MD5 digest=");
2923 for (i
= 0; i
< maxlen
; ++i
) {
2924 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2926 fprintf (stderr
, "\n");
2932 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2933 (int) strlen ((char *) digest
));
2943 /* Multipart parts might have content before the first subpart and/or
2944 after the last subpart that hasn't been stored anywhere else, so do
2947 get_leftover_mp_content (CT ct
, int before
/* or after */)
2949 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2951 int found_boundary
= 0;
2957 char *content
= NULL
;
2959 if (! m
) return NOTOK
;
2962 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2964 /* Isolate the beginning of this part to the beginning of the
2965 first subpart and save any content between them. */
2966 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2967 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2968 boundary
= concat ("--", m
->mp_start
, NULL
);
2970 struct part
*last_subpart
= NULL
;
2971 struct part
*subpart
;
2973 /* Go to the last subpart to get its end position. */
2974 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2975 last_subpart
= subpart
;
2978 if (last_subpart
== NULL
) return NOTOK
;
2980 /* Isolate the end of the last subpart to the end of this part
2981 and save any content between them. */
2982 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2983 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2984 boundary
= concat ("--", m
->mp_stop
, NULL
);
2987 /* Back up by 1 to pick up the newline. */
2988 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2990 /* Don't look beyond beginning of first subpart (before) or
2991 next part (after). */
2992 if (read
> max
) bufp
[read
-max
] = '\0';
2995 if (! strcmp (bufp
, boundary
)) {
2999 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3005 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3007 char *old_content
= content
;
3008 content
= concat (content
, bufp
, NULL
);
3012 ? concat ("\n", bufp
, NULL
)
3013 : concat (bufp
, NULL
);
3018 if (found_boundary
|| read
> max
) break;
3020 if (read
> max
) break;
3024 /* Skip the newline if that's all there is. */
3028 /* Remove trailing newline, except at EOF. */
3029 if ((before
|| ! feof (ct
->c_fp
)) &&
3030 (cp
= content
+ strlen (content
)) > content
&&
3035 if (strlen (content
) > 1) {
3037 m
->mp_content_before
= content
;
3039 m
->mp_content_after
= content
;
3054 ct_type_str (int type
) {
3056 case CT_APPLICATION
:
3057 return "application";
3073 return "unknown_type";
3079 ct_subtype_str (int type
, int subtype
) {
3081 case CT_APPLICATION
:
3083 case APPLICATION_OCTETS
:
3085 case APPLICATION_POSTSCRIPT
:
3086 return "postscript";
3088 return "unknown_app_subtype";
3092 case MESSAGE_RFC822
:
3094 case MESSAGE_PARTIAL
:
3096 case MESSAGE_EXTERNAL
:
3099 return "unknown_msg_subtype";
3105 case MULTI_ALTERNATE
:
3106 return "alternative";
3109 case MULTI_PARALLEL
:
3114 return "unknown_multipart_subtype";
3125 return "unknown_text_subtype";
3128 return "unknown_type";
3134 ct_str_type (const char *type
) {
3135 struct str2init
*s2i
;
3137 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3138 if (! strcasecmp (type
, s2i
->si_key
)) {
3142 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3151 ct_str_subtype (int type
, const char *subtype
) {
3155 case CT_APPLICATION
:
3156 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3157 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3161 return kv
->kv_value
;
3163 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3164 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3168 return kv
->kv_value
;
3170 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3171 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3175 return kv
->kv_value
;
3177 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3178 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3182 return kv
->kv_value
;
3189 /* Find the content type and InitFunc for the CT. */
3190 const struct str2init
*
3191 get_ct_init (int type
) {
3192 const struct str2init
*sp
;
3194 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3195 if (type
== sp
->si_val
) {
3204 ce_str (int encoding
) {
3209 return "quoted-printable";
3225 /* Find the content type and InitFunc for the content encoding method. */
3226 const struct str2init
*
3227 get_ce_method (const char *method
) {
3228 struct str2init
*sp
;
3230 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3231 if (! strcasecmp (method
, sp
->si_key
)) {
3240 * Parse a series of MIME attributes (or parameters) given a header as
3243 * Arguments include:
3245 * filename - Name of input file (for error messages)
3246 * fieldname - Name of field being processed
3247 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3248 * Updated to point to end of attributes when finished.
3249 * param_head - Pointer to head of parameter list
3250 * param_tail - Pointer to tail of parameter list
3251 * commentp - Pointer to header comment pointer (may be NULL)
3253 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3254 * DONE to indicate a benign error (minor parsing error, but the program
3259 parse_header_attrs (const char *filename
, const char *fieldname
,
3260 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3263 char *cp
= *header_attrp
;
3269 struct sectlist
*next
;
3275 struct sectlist
*sechead
;
3276 struct parmlist
*next
;
3277 } *pp
, *pp2
, *phead
= NULL
;
3279 while (*cp
== ';') {
3280 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3281 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3284 while (isspace ((unsigned char) *cp
))
3288 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3293 if (! suppress_extraneous_trailing_semicolon_warning
) {
3294 inform("extraneous trailing ';' in message %s's %s: "
3295 "parameter list", filename
, fieldname
);
3300 /* down case the attribute name */
3301 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3302 *dp
= tolower ((unsigned char) *dp
);
3304 for (up
= dp
; isspace ((unsigned char) *dp
);)
3306 if (dp
== cp
|| *dp
!= '=') {
3307 inform("invalid parameter in message %s's %s: "
3308 "field\n%*sparameter %s (error detected at offset %d)",
3309 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3314 * To handle RFC 2231, we have to deal with the following extensions:
3316 * name*=encoded-value
3317 * name*<N>=part-N-of-a-parameter-value
3318 * name*<N>*=encoded-part-N-of-a-parameter-value
3321 * If there's a * right before the equal sign, it's encoded.
3322 * If there's a * and one or more digits, then it's section N.
3324 * Remember we can have one or the other, or both. cp points to
3325 * beginning of name, up points past the last character in the
3329 for (vp
= cp
; vp
< up
; vp
++) {
3330 if (*vp
== '*' && vp
< up
- 1) {
3334 if (*vp
== '*' && vp
== up
- 1) {
3336 } else if (partial
) {
3337 if (isdigit((unsigned char) *vp
))
3338 index
= *vp
- '0' + index
* 10;
3340 inform("invalid parameter index in message %s's "
3341 "%s: field\n%*s(parameter %s)", filename
,
3342 fieldname
, strlen(invo_name
) + 2, "", cp
);
3351 * Break out the parameter name and value sections and allocate
3355 nameptr
= mh_xmalloc(len
+ 1);
3356 strncpy(nameptr
, cp
, len
);
3357 nameptr
[len
] = '\0';
3359 for (dp
++; isspace ((unsigned char) *dp
);)
3364 * Single quotes delimit the character set and language tag.
3365 * They are required on the first section (or a complete
3370 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3376 charset
= mh_xmalloc(len
+ 1);
3377 strncpy(charset
, dp
, len
);
3378 charset
[len
] = '\0';
3384 inform("missing charset in message %s's %s: "
3385 "field\n%*s(parameter %s)", filename
, fieldname
,
3386 strlen(invo_name
) + 2, "", nameptr
);
3392 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3399 lang
= mh_xmalloc(len
+ 1);
3400 strncpy(lang
, dp
, len
);
3407 inform("missing language tag in message %s's %s: "
3408 "field\n%*s(parameter %s)", filename
, fieldname
,
3409 strlen(invo_name
) + 2, "", nameptr
);
3419 * At this point vp should be pointing at the beginning
3420 * of the encoded value/section. Continue until we reach
3421 * the end or get whitespace. But first, calculate the
3422 * length so we can allocate the correct buffer size.
3425 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3427 if (*(vp
+ 1) == '\0' ||
3428 !isxdigit((unsigned char) *(vp
+ 1)) ||
3429 *(vp
+ 2) == '\0' ||
3430 !isxdigit((unsigned char) *(vp
+ 2))) {
3431 inform("invalid encoded sequence in message "
3432 "%s's %s: field\n%*s(parameter %s)",
3433 filename
, fieldname
, strlen(invo_name
) + 2,
3445 up
= valptr
= mh_xmalloc(len
+ 1);
3447 for (vp
= dp
; istoken(*vp
); vp
++) {
3449 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3460 * A "normal" string. If it's got a leading quote, then we
3461 * strip the quotes out. Otherwise go until we reach the end
3462 * or get whitespace. Note we scan it twice; once to get the
3463 * length, then the second time copies it into the destination
3470 for (cp
= dp
+ 1;;) {
3474 inform("invalid quoted-string in message %s's %s: "
3475 "field\n%*s(parameter %s)", filename
,
3476 fieldname
, strlen(invo_name
) + 2, "", nameptr
);
3496 for (cp
= dp
; istoken (*cp
); cp
++) {
3501 valptr
= mh_xmalloc(len
+ 1);
3505 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3513 strncpy(valptr
, cp
= dp
, len
);
3521 * If 'partial' is set, we don't allocate a parameter now. We
3522 * put it on the parameter linked list to be reassembled later.
3524 * "phead" points to a list of all parameters we need to reassemble.
3525 * Each parameter has a list of sections. We insert the sections in
3530 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3531 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3546 * Insert this into the section linked list
3554 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3555 sp
->next
= pp
->sechead
;
3558 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3559 if (sp2
->index
== sp
->index
) {
3560 inform("duplicate index (%d) in message "
3561 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3562 filename
, fieldname
, strlen(invo_name
) + 2, "",
3566 if (sp2
->index
< sp
->index
&&
3567 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3568 sp
->next
= sp2
->next
;
3575 inform("Internal error: cannot insert partial "
3576 "param in message %s's %s: field\n%*s(parameter %s)",
3577 filename
, fieldname
, strlen(invo_name
) + 2, "",
3584 * Save our charset and lang tags.
3587 if (index
== 0 && encoded
) {
3588 mh_xfree(pp
->charset
);
3589 pp
->charset
= charset
;
3594 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3595 pm
->pm_charset
= charset
;
3599 while (isspace ((unsigned char) *cp
))
3603 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3609 * Now that we're done, reassemble all of the partial parameters.
3612 for (pp
= phead
; pp
!= NULL
; ) {
3616 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3617 if (sp
->index
!= pindex
++) {
3618 inform("missing section %d for parameter in "
3619 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3620 filename
, fieldname
, strlen(invo_name
) + 2, "",
3627 p
= q
= mh_xmalloc(tlen
+ 1);
3628 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3629 memcpy(q
, sp
->value
, sp
->len
);
3639 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3640 pm
->pm_charset
= pp
->charset
;
3641 pm
->pm_lang
= pp
->lang
;
3652 * Return the charset for a particular content type.
3656 content_charset (CT ct
) {
3657 char *ret_charset
= NULL
;
3659 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3661 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3666 * Create a string based on a list of output parameters. Assume that this
3667 * parameter string will be appended to an existing header, so start out
3668 * with the separator (;). Perform RFC 2231 encoding when necessary.
3672 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3674 char *paramout
= NULL
;
3675 char line
[CPERLIN
* 2], *q
;
3676 int curlen
, index
, cont
, encode
, i
;
3677 size_t valoff
, numchars
;
3679 while (params
!= NULL
) {
3685 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3688 if (strlen(params
->pm_name
) > CPERLIN
) {
3689 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3694 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3697 * Loop until we get a parameter that fits within a line. We
3698 * assume new lines start with a tab, so check our overflow based
3708 * At this point we're definitely continuing the line, so
3709 * be sure to include the parameter name and section index.
3712 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3713 params
->pm_name
, index
);
3716 * Both of these functions do a NUL termination
3720 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3721 numchars
, valoff
, index
);
3723 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3733 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3738 * "line" starts with a ;\n\t, so that doesn't count against
3739 * the length. But add 8 since it starts with a tab; that's
3740 * how we end up with 5.
3743 initialwidth
= strlen(line
) + 5;
3746 * At this point the line should be built, so add it to our
3747 * current output buffer.
3750 paramout
= add(line
, paramout
);
3754 * If this won't fit on the line, start a new one. Save room in
3755 * case we need a semicolon on the end
3758 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3770 * At this point, we're either finishing a continued parameter, or
3771 * we're working on a new one.
3775 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3776 params
->pm_name
, index
);
3778 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3783 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3784 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3786 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3787 strlen(params
->pm_value
+ valoff
), valoff
);
3794 paramout
= add(line
, paramout
);
3795 initialwidth
+= strlen(line
);
3797 params
= params
->pm_next
;
3801 *offsetout
= initialwidth
;
3807 * Calculate the size of a parameter.
3811 * pm - The parameter being output
3812 * index - If continuing the parameter, the index of the section
3814 * valueoff - The current offset into the parameter value that we're
3815 * working on (previous sections have consumed valueoff bytes).
3816 * encode - Set if we should perform encoding on this parameter section
3817 * (given that we're consuming bytesfit bytes).
3818 * cont - Set if the remaining data in value will not fit on a single
3819 * line and will need to be continued.
3820 * bytesfit - The number of bytes that we can consume from the parameter
3821 * value and still fit on a completely new line. The
3822 * calculation assumes the new line starts with a tab,
3823 * includes the parameter name and any encoding, and fits
3824 * within CPERLIN bytes. Will always be at least 1.
3828 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3831 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3832 size_t len
= 0, fit
= 0;
3833 int fitlimit
= 0, eightbit
, maxfit
;
3838 * Add up the length. First, start with the parameter name.
3841 len
= strlen(pm
->pm_name
);
3844 * Scan the parameter value and see if we need to do encoding for this
3848 eightbit
= contains8bit(start
, NULL
);
3851 * Determine if we need to encode this section. Encoding is necessary if:
3853 * - There are any 8-bit characters at all and we're on the first
3855 * - There are 8-bit characters within N bytes of our section start.
3856 * N is calculated based on the number of bytes it would take to
3857 * reach CPERLIN. Specifically:
3858 * 8 (starting tab) +
3859 * strlen(param name) +
3860 * 4 ('* for section marker, '=', opening/closing '"')
3862 * is the number of bytes used by everything that isn't part of the
3863 * value. So that gets subtracted from CPERLIN.
3866 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3867 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3868 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3872 len
++; /* Add in equal sign */
3876 * We're using maxfit as a marker for how many characters we can
3877 * fit into the line. Bump it by two because we're not using quotes
3884 * If we don't have a charset or language tag in this parameter,
3888 if (! pm
->pm_charset
) {
3889 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3890 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3891 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3892 "local character set is US-ASCII", pm
->pm_name
);
3895 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3897 len
++; /* For the encoding marker */
3900 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3905 * We know we definitely need to include an index. maxfit already
3906 * includes the section marker.
3908 len
+= strlen(indexchar
);
3910 for (p
= start
; *p
!= '\0'; p
++) {
3911 if (isparamencode(*p
)) {
3919 * Just so there's no confusion: maxfit is counting OUTPUT
3920 * characters (post-encoding). fit is counting INPUT characters.
3922 if (! fitlimit
&& maxfit
>= 0)
3924 else if (! fitlimit
)
3929 * Calculate the string length, but add room for quoting \
3930 * and " if necessary. Also account for quotes at beginning
3933 for (p
= start
; *p
!= '\0'; p
++) {
3944 if (! fitlimit
&& maxfit
>= 0)
3946 else if (! fitlimit
)
3963 * Output an encoded parameter string.
3967 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3968 size_t valueoff
, int index
)
3970 size_t outlen
= 0, n
;
3971 char *endptr
= output
+ len
, *p
;
3974 * First, output the marker for an encoded string.
3982 * If the index is 0, output the character set and language tag.
3983 * If theses were NULL, they should have already been filled in
3988 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3992 if (output
> endptr
) {
3993 inform("Internal error: parameter buffer overflow");
3999 * Copy over the value, encoding if necessary
4002 p
= pm
->pm_value
+ valueoff
;
4003 while (valuelen
-- > 0) {
4004 if (isparamencode(*p
)) {
4005 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4012 if (output
> endptr
) {
4013 inform("Internal error: parameter buffer overflow");
4024 * Output a "normal" parameter, without encoding. Be sure to escape
4025 * quotes and backslashes if necessary.
4029 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4033 char *endptr
= output
+ len
, *p
;
4039 p
= pm
->pm_value
+ valueoff
;
4041 while (valuelen
-- > 0) {
4052 if (output
> endptr
) {
4053 inform("Internal error: parameter buffer overflow");
4058 if (output
- 2 > endptr
) {
4059 inform("Internal error: parameter buffer overflow");
4070 * Add a parameter to the parameter linked list
4074 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4079 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4080 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4083 (*last
)->pm_next
= pm
;
4094 * Either replace a current parameter with a new value, or add the parameter
4095 * to the parameter linked list.
4099 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4103 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4104 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4106 * If nocopy is set, it's assumed that we own both name
4107 * and value. We don't need name, so we discard it now.
4112 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4117 return add_param(first
, last
, name
, value
, nocopy
);
4121 * Retrieve a parameter value from a parameter linked list. If the parameter
4122 * value needs converted to the local character set, do that now.
4126 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4128 while (first
!= NULL
) {
4129 if (strcasecmp(name
, first
->pm_name
) == 0) {
4131 return first
->pm_value
;
4132 return getcpy(get_param_value(first
, replace
));
4134 first
= first
->pm_next
;
4141 * Return a parameter value, converting to the local character set if
4145 char *get_param_value(PM pm
, char replace
)
4147 static char buffer
[4096]; /* I hope no parameters are larger */
4148 size_t bufsize
= sizeof(buffer
);
4153 ICONV_CONST
char *p
;
4154 #else /* HAVE_ICONV */
4156 #endif /* HAVE_ICONV */
4161 * If we don't have a character set indicated, it's assumed to be
4162 * US-ASCII. If it matches our character set, we don't need to convert
4166 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4167 strlen(pm
->pm_charset
))) {
4168 return pm
->pm_value
;
4172 * In this case, we need to convert. If we have iconv support, use
4173 * that. Otherwise, go through and simply replace every non-ASCII
4174 * character with the substitution character.
4179 bufsize
= sizeof(buffer
);
4180 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4182 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4183 if (cd
== (iconv_t
) -1) {
4187 inbytes
= strlen(pm
->pm_value
);
4191 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4192 if (errno
!= EILSEQ
) {
4197 * Reset shift state, substitute our character,
4198 * try to restart conversion.
4201 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4214 for (++p
, --inbytes
;
4215 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4234 #endif /* HAVE_ICONV */
4237 * Take everything non-ASCII and substitute the replacement character
4241 bufsize
= sizeof(buffer
);
4242 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4243 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))