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.
9 #include "sbr/pidstatus.h"
10 #include "sbr/arglist.h"
11 #include "sbr/error.h"
17 #include "h/mhparse.h"
20 #include "h/mhcachesbr.h"
21 #include "sbr/m_mktemp.h"
25 #endif /* HAVE_ICONV */
26 #include "sbr/base64.h"
31 int checksw
= 0; /* check Content-MD5 field */
34 * These are for mhfixmsg to:
35 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
37 * 2) Suppress the warning about bogus multipart content, and report it.
38 * 3) Suppress the warning about extraneous trailing ';' in header parameter
41 bool skip_mp_cte_check
;
42 bool suppress_bogus_mp_content_warning
;
43 bool bogus_mp_content
;
44 bool suppress_extraneous_trailing_semicolon_warning
;
47 * By default, suppress warning about multiple MIME-Version header fields.
49 bool suppress_multiple_mime_version_warning
= true;
51 /* list of preferred type/subtype pairs, for -prefer */
52 mime_type_subtype mime_preference
[NPREFS
];
57 * Structures for TEXT messages
59 struct k2v SubText
[] = {
60 { "plain", TEXT_PLAIN
},
61 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
62 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
63 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
66 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
69 * Structures for MULTIPART messages
71 struct k2v SubMultiPart
[] = {
72 { "mixed", MULTI_MIXED
},
73 { "alternative", MULTI_ALTERNATE
},
74 { "digest", MULTI_DIGEST
},
75 { "parallel", MULTI_PARALLEL
},
76 { "related", MULTI_RELATED
},
77 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
81 * Structures for MESSAGE messages
83 struct k2v SubMessage
[] = {
84 { "rfc822", MESSAGE_RFC822
},
85 { "partial", MESSAGE_PARTIAL
},
86 { "external-body", MESSAGE_EXTERNAL
},
87 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
91 * Structure for APPLICATION messages
93 struct k2v SubApplication
[] = {
94 { "octet-stream", APPLICATION_OCTETS
},
95 { "postscript", APPLICATION_POSTSCRIPT
},
96 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
100 * Mapping of names of CTE types in mhbuild directives
102 static struct k2v EncodingType
[] = {
105 { "q-p", CE_QUOTED
},
106 { "quoted-printable", CE_QUOTED
},
107 { "b64", CE_BASE64
},
108 { "base64", CE_BASE64
},
116 static CT
get_content (FILE *, char *, int);
117 static int get_comment (const char *, const char *, char **, char **);
119 static int InitGeneric (CT
);
120 static int InitText (CT
);
121 static int InitMultiPart (CT
);
122 static void reverse_parts (CT
);
123 static void prefer_parts(CT ct
);
124 static int InitMessage (CT
);
125 static int InitApplication (CT
);
126 static int init_encoding (CT
, OpenCEFunc
);
127 static unsigned long size_encoding (CT
);
128 static int InitBase64 (CT
);
129 static int openBase64 (CT
, char **);
130 static int InitQuoted (CT
);
131 static int openQuoted (CT
, char **);
132 static int Init7Bit (CT
);
133 static int openExternal (CT
, CT
, CE
, char **, int *);
134 static int InitFile (CT
);
135 static int openFile (CT
, char **);
136 static int InitFTP (CT
);
137 static int openFTP (CT
, char **);
138 static int InitMail (CT
);
139 static int openMail (CT
, char **);
140 static int readDigest (CT
, char *);
141 static int get_leftover_mp_content (CT
, int);
142 static int InitURL (CT
);
143 static int openURL (CT
, char **);
144 static int parse_header_attrs (const char *, const char *, char **, PM
*,
146 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
147 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
148 static int get_dispo (char *, CT
, int);
150 struct str2init str2cts
[] = {
151 { "application", CT_APPLICATION
, InitApplication
},
152 { "audio", CT_AUDIO
, InitGeneric
},
153 { "image", CT_IMAGE
, InitGeneric
},
154 { "message", CT_MESSAGE
, InitMessage
},
155 { "multipart", CT_MULTIPART
, InitMultiPart
},
156 { "text", CT_TEXT
, InitText
},
157 { "video", CT_VIDEO
, InitGeneric
},
158 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
159 { NULL
, CT_UNKNOWN
, NULL
},
162 struct str2init str2ces
[] = {
163 { "base64", CE_BASE64
, InitBase64
},
164 { "quoted-printable", CE_QUOTED
, InitQuoted
},
165 { "8bit", CE_8BIT
, Init7Bit
},
166 { "7bit", CE_7BIT
, Init7Bit
},
167 { "binary", CE_BINARY
, Init7Bit
},
168 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
169 { NULL
, CE_UNKNOWN
, NULL
},
173 * NOTE WELL: si_key MUST NOT have value of NOTOK
175 * si_val is 1 if access method is anonymous.
177 struct str2init str2methods
[] = {
178 { "afs", 1, InitFile
},
179 { "anon-ftp", 1, InitFTP
},
180 { "ftp", 0, InitFTP
},
181 { "local-file", 0, InitFile
},
182 { "mail-server", 0, InitMail
},
183 { "url", 0, InitURL
},
189 * Main entry point for parsing a MIME message or file.
190 * It returns the Content structure for the top level
191 * entity in the file.
195 parse_mime (char *file
)
204 bogus_mp_content
= false;
207 * Check if file is actually standard input
209 if ((is_stdin
= !(strcmp (file
, "-")))) {
210 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
212 advise("mhparse", "unable to create temporary file in %s",
216 file
= mh_xstrdup(tfile
);
218 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
219 if (fwrite(buffer
, 1, n
, fp
) != n
) {
220 (void) m_unlink (file
);
221 advise (file
, "error copying to temporary file");
227 if (ferror (stdin
)) {
228 (void) m_unlink (file
);
229 advise ("stdin", "error reading");
233 (void) m_unlink (file
);
234 advise (file
, "error writing");
237 fseek (fp
, 0L, SEEK_SET
);
238 } else if (stat (file
, &statbuf
) == NOTOK
) {
239 advise (file
, "unable to stat");
241 } else if (S_ISDIR(statbuf
.st_mode
)) {
242 /* Don't try to parse a directory. */
243 inform("%s is a directory", file
);
245 } else if ((fp
= fopen (file
, "r")) == NULL
) {
246 advise (file
, "unable to read");
250 if (!(ct
= get_content (fp
, file
, 1))) {
252 (void) m_unlink (file
);
253 inform("unable to decode %s", file
);
258 ct
->c_unlink
= 1; /* temp file to remove */
262 if (ct
->c_end
== 0L) {
263 fseek (fp
, 0L, SEEK_END
);
264 ct
->c_end
= ftell (fp
);
267 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
279 * Main routine for reading/parsing the headers
280 * of a message content.
282 * toplevel = 1 # we are at the top level of the message
283 * toplevel = 0 # we are inside message type or multipart type
284 * # other than multipart/digest
285 * toplevel = -1 # we are inside multipart/digest
286 * NB: on failure we will fclose(in)!
290 get_content (FILE *in
, char *file
, int toplevel
)
293 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
297 m_getfld_state_t gstate
;
299 /* allocate the content structure */
302 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
303 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
306 * Parse the header fields for this
307 * content into a linked list.
309 gstate
= m_getfld_state_init(in
);
310 m_getfld_track_filepos2(&gstate
);
311 for (compnum
= 1;;) {
312 int bufsz
= sizeof buf
;
313 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
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_getfld2(&gstate
, name
, buf
, &bufsz
);
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 die("message format error in component #%d", compnum
);
370 die("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
= mh_xstrdup(FENDNULL(hp
->value
));
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
= mh_xstrdup(FENDNULL(hp
->value
));
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
= mh_xstrdup(FENDNULL(hp
->value
)); /* 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
= mh_xstrdup(FENDNULL(cp
));
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"
868 " (%s)", ct
->c_file
, TYPE_FIELD
, 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
= mh_xstrdup(FENDNULL(cp
));
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)",
937 ct
->c_file
, DISPO_FIELD
, 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
; 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
= true;
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
, mime_type_subtype
*pref
)
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
, pref
->type
) &&
1348 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1349 prev
->mp_next
= part
->mp_next
;
1350 part
->mp_next
= NULL
;
1351 ntail
->mp_next
= part
;
1353 part
= prev
->mp_next
;
1356 part
= prev
->mp_next
;
1359 ntail
->mp_next
= head
->mp_next
;
1360 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
= 0; i
< npreferred
; i
++)
1373 move_preferred_part(ct
, mime_preference
+ 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
)
1385 if (ct
->c_type
== CT_MULTIPART
) {
1386 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1389 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1393 /* And call recursively on each part of a multipart. */
1394 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1395 reverse_alternative_parts (part
->mp_part
);
1408 CI ci
= &ct
->c_ctinfo
;
1410 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1411 inform("\"%s/%s\" type in message %s should be encoded in "
1412 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1417 /* check for missing subtype */
1418 if (!*ci
->ci_subtype
)
1419 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1422 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1424 switch (ct
->c_subtype
) {
1425 case MESSAGE_RFC822
:
1428 case MESSAGE_PARTIAL
:
1434 ct
->c_ctparams
= (void *) p
;
1436 /* scan for parameters "id", "number", and "total" */
1437 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1438 if (!strcasecmp (pm
->pm_name
, "id")) {
1439 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1442 if (!strcasecmp (pm
->pm_name
, "number")) {
1443 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1444 || p
->pm_partno
< 1) {
1446 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1447 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1448 ct
->c_file
, TYPE_FIELD
);
1453 if (!strcasecmp (pm
->pm_name
, "total")) {
1454 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1463 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1464 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1465 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1471 case MESSAGE_EXTERNAL
:
1479 ct
->c_ctparams
= (void *) e
;
1482 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1483 advise (ct
->c_file
, "unable to open for reading");
1487 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1489 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1497 p
->c_ceopenfnx
= NULL
;
1498 if ((exresult
= params_external (ct
, 0)) != NOTOK
1499 && p
->c_ceopenfnx
== openMail
) {
1503 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1505 content_error (NULL
, ct
,
1506 "empty body for access-type=mail-server");
1510 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1511 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1513 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1515 adios ("failed", "fread");
1518 die("unexpected EOF from fread");
1521 bp
+= cc
, size
-= cc
;
1528 p
->c_end
= p
->c_begin
;
1533 if (exresult
== NOTOK
)
1535 if (e
->eb_flags
== NOTOK
)
1538 switch (p
->c_type
) {
1543 if (p
->c_subtype
!= MESSAGE_RFC822
)
1547 e
->eb_partno
= ct
->c_partno
;
1549 (*p
->c_ctinitfnx
) (p
);
1564 params_external (CT ct
, int composing
)
1567 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1568 CI ci
= &ct
->c_ctinfo
;
1570 ct
->c_ceopenfnx
= NULL
;
1571 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1572 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1573 struct str2init
*s2i
;
1574 CT p
= e
->eb_content
;
1576 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1577 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1580 e
->eb_access
= pm
->pm_value
;
1581 e
->eb_flags
= NOTOK
;
1582 p
->c_encoding
= CE_EXTERNAL
;
1585 e
->eb_access
= s2i
->si_key
;
1586 e
->eb_flags
= s2i
->si_val
;
1587 p
->c_encoding
= CE_EXTERNAL
;
1589 /* Call the Init function for this external type */
1590 if ((*s2i
->si_init
)(p
) == NOTOK
)
1594 if (!strcasecmp (pm
->pm_name
, "name")) {
1595 e
->eb_name
= pm
->pm_value
;
1598 if (!strcasecmp (pm
->pm_name
, "permission")) {
1599 e
->eb_permission
= pm
->pm_value
;
1602 if (!strcasecmp (pm
->pm_name
, "site")) {
1603 e
->eb_site
= pm
->pm_value
;
1606 if (!strcasecmp (pm
->pm_name
, "directory")) {
1607 e
->eb_dir
= pm
->pm_value
;
1610 if (!strcasecmp (pm
->pm_name
, "mode")) {
1611 e
->eb_mode
= pm
->pm_value
;
1614 if (!strcasecmp (pm
->pm_name
, "size")) {
1615 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1618 if (!strcasecmp (pm
->pm_name
, "server")) {
1619 e
->eb_server
= pm
->pm_value
;
1622 if (!strcasecmp (pm
->pm_name
, "subject")) {
1623 e
->eb_subject
= pm
->pm_value
;
1626 if (!strcasecmp (pm
->pm_name
, "url")) {
1628 * According to RFC 2017, we have to remove all whitespace from
1632 char *u
, *p
= pm
->pm_value
;
1633 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1635 for (; *p
!= '\0'; p
++) {
1636 if (! isspace((unsigned char) *p
))
1643 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1644 e
->eb_body
= getcpy (pm
->pm_value
);
1649 if (!e
->eb_access
) {
1650 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1651 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1664 InitApplication (CT ct
)
1666 CI ci
= &ct
->c_ctinfo
;
1669 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1676 * TRANSFER ENCODINGS
1680 init_encoding (CT ct
, OpenCEFunc openfnx
)
1682 ct
->c_ceopenfnx
= openfnx
;
1683 ct
->c_ceclosefnx
= close_encoding
;
1684 ct
->c_cesizefnx
= size_encoding
;
1691 close_encoding (CT ct
)
1693 CE ce
= &ct
->c_cefile
;
1702 static unsigned long
1703 size_encoding (CT ct
)
1708 CE ce
= &ct
->c_cefile
;
1711 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1712 return (long) st
.st_size
;
1715 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1716 return (long) st
.st_size
;
1720 if (ct
->c_encoding
== CE_EXTERNAL
)
1721 return ct
->c_end
- ct
->c_begin
;
1724 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1725 return ct
->c_end
- ct
->c_begin
;
1727 if (fstat (fd
, &st
) != NOTOK
)
1728 size
= (long) st
.st_size
;
1732 (*ct
->c_ceclosefnx
) (ct
);
1744 return init_encoding (ct
, openBase64
);
1749 openBase64 (CT ct
, char **file
)
1753 bool own_ct_fp
= false;
1754 char *cp
, *buffer
= NULL
;
1755 /* sbeck -- handle suffixes */
1757 CE ce
= &ct
->c_cefile
;
1758 unsigned char *decoded
;
1760 unsigned char digest
[16];
1763 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1768 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1769 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1775 if (*file
== NULL
) {
1778 ce
->ce_file
= mh_xstrdup(*file
);
1782 /* sbeck@cise.ufl.edu -- handle suffixes */
1784 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1785 if (ce
->ce_unlink
) {
1786 /* Create temporary file with filename extension. */
1787 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1788 die("unable to create temporary file in %s",
1792 ce
->ce_file
= add (cp
, ce
->ce_file
);
1794 } else if (*file
== NULL
) {
1796 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1797 die("unable to create temporary file in %s",
1800 ce
->ce_file
= mh_xstrdup(tempfile
);
1803 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1804 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1808 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1809 die("internal error(1)");
1811 buffer
= mh_xmalloc (len
+ 1);
1814 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1815 content_error (ct
->c_file
, ct
, "unable to open for reading");
1821 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1824 switch (cc
= read (fd
, cp
, len
)) {
1826 content_error (ct
->c_file
, ct
, "error reading from");
1830 content_error (NULL
, ct
, "premature eof");
1841 /* decodeBase64() requires null-terminated input. */
1844 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1845 ct
->c_digested
? digest
: NULL
) != OK
)
1850 unsigned char *decoded_p
= decoded
;
1851 for (i
= 0; i
< decoded_len
; ++i
) {
1852 putc (*decoded_p
++, ce
->ce_fp
);
1855 if (ferror (ce
->ce_fp
)) {
1856 content_error (ce
->ce_file
, ct
, "error writing to");
1860 if (ct
->c_digested
) {
1861 if (memcmp(digest
, ct
->c_digest
,
1863 content_error (NULL
, ct
,
1864 "content integrity suspect (digest mismatch) -- continuing");
1867 fprintf (stderr
, "content integrity confirmed\n");
1873 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1875 if (fflush (ce
->ce_fp
)) {
1876 content_error (ce
->ce_file
, ct
, "error writing to");
1880 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1883 *file
= ce
->ce_file
;
1889 return fileno (ce
->ce_fp
);
1896 free_encoding (ct
, 0);
1906 static char hex2nib
[0x80] = {
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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1911 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1913 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1914 0x08, 0x09, 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,
1919 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1920 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1921 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1922 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1929 return init_encoding (ct
, openQuoted
);
1934 openQuoted (CT ct
, char **file
)
1936 int cc
, digested
, len
, quoted
;
1937 bool own_ct_fp
= false;
1943 CE ce
= &ct
->c_cefile
;
1944 /* sbeck -- handle suffixes */
1949 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1954 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1955 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1961 if (*file
== NULL
) {
1964 ce
->ce_file
= mh_xstrdup(*file
);
1968 /* sbeck@cise.ufl.edu -- handle suffixes */
1970 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1971 if (ce
->ce_unlink
) {
1972 /* Create temporary file with filename extension. */
1973 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1974 die("unable to create temporary file in %s",
1978 ce
->ce_file
= add (cp
, ce
->ce_file
);
1980 } else if (*file
== NULL
) {
1982 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1983 die("unable to create temporary file in %s",
1986 ce
->ce_file
= mh_xstrdup(tempfile
);
1989 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1990 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1994 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1995 die("internal error(2)");
1998 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1999 content_error (ct
->c_file
, ct
, "unable to open for reading");
2005 if ((digested
= ct
->c_digested
))
2006 MD5Init (&mdContext
);
2010 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2012 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2013 content_error (NULL
, ct
, "premature eof");
2017 if ((cc
= gotlen
) > len
)
2021 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2022 if (!isspace ((unsigned char) *ep
))
2027 for (; cp
< ep
; cp
++) {
2029 /* in an escape sequence */
2031 /* at byte 1 of an escape sequence */
2032 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2033 /* next is byte 2 */
2036 /* at byte 2 of an escape sequence */
2038 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2039 putc (mask
, ce
->ce_fp
);
2041 MD5Update (&mdContext
, &mask
, 1);
2042 if (ferror (ce
->ce_fp
)) {
2043 content_error (ce
->ce_file
, ct
, "error writing to");
2046 /* finished escape sequence; next may be literal or a new
2047 * escape sequence */
2050 /* on to next byte */
2054 /* not in an escape sequence */
2056 /* starting an escape sequence, or invalid '='? */
2057 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2058 /* "=\n" soft line break, eat the \n */
2062 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2063 /* We don't have 2 bytes left, so this is an invalid
2064 * escape sequence; just show the raw bytes (below). */
2065 } else if (isxdigit ((unsigned char) cp
[1]) &&
2066 isxdigit ((unsigned char) cp
[2])) {
2067 /* Next 2 bytes are hex digits, making this a valid escape
2068 * sequence; let's decode it (above). */
2072 /* One or both of the next 2 is out of range, making this
2073 * an invalid escape sequence; just show the raw bytes
2077 /* Just show the raw byte. */
2078 putc (*cp
, ce
->ce_fp
);
2081 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2083 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2086 if (ferror (ce
->ce_fp
)) {
2087 content_error (ce
->ce_file
, ct
, "error writing to");
2093 content_error (NULL
, ct
,
2094 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2098 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2100 if (fflush (ce
->ce_fp
)) {
2101 content_error (ce
->ce_file
, ct
, "error writing to");
2106 unsigned char digest
[16];
2108 MD5Final (digest
, &mdContext
);
2109 if (memcmp(digest
, ct
->c_digest
,
2111 content_error (NULL
, ct
,
2112 "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
)
2158 bool own_ct_fp
= false;
2159 char buffer
[BUFSIZ
];
2160 /* sbeck -- handle suffixes */
2163 CE ce
= &ct
->c_cefile
;
2166 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2171 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2172 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2178 if (*file
== NULL
) {
2181 ce
->ce_file
= mh_xstrdup(*file
);
2185 /* sbeck@cise.ufl.edu -- handle suffixes */
2187 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2188 if (ce
->ce_unlink
) {
2189 /* Create temporary file with filename extension. */
2190 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2191 die("unable to create temporary file in %s",
2195 ce
->ce_file
= add (cp
, ce
->ce_file
);
2197 } else if (*file
== NULL
) {
2199 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2200 die("unable to create temporary file in %s",
2203 ce
->ce_file
= mh_xstrdup(tempfile
);
2206 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2207 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2211 if (ct
->c_type
== CT_MULTIPART
) {
2212 CI ci
= &ct
->c_ctinfo
;
2216 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2217 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2218 + 1 + strlen (ci
->ci_subtype
);
2219 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2222 fputs (buffer
, ce
->ce_fp
);
2226 if (ci
->ci_comment
) {
2227 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2228 fputs ("\n\t", ce
->ce_fp
);
2232 putc (' ', ce
->ce_fp
);
2235 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2238 fprintf (ce
->ce_fp
, "\n");
2240 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2242 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2244 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2245 fprintf (ce
->ce_fp
, "\n");
2248 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2249 die("internal error(3)");
2252 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2253 content_error (ct
->c_file
, ct
, "unable to open for reading");
2259 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2261 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2263 content_error (ct
->c_file
, ct
, "error reading from");
2267 content_error (NULL
, ct
, "premature eof");
2275 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2276 advise ("open7Bit", "fwrite");
2278 if (ferror (ce
->ce_fp
)) {
2279 content_error (ce
->ce_file
, ct
, "error writing to");
2284 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2286 if (fflush (ce
->ce_fp
)) {
2287 content_error (ce
->ce_file
, ct
, "error writing to");
2291 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2294 *file
= ce
->ce_file
;
2299 return fileno (ce
->ce_fp
);
2302 free_encoding (ct
, 0);
2316 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2318 char cachefile
[BUFSIZ
];
2321 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2326 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2327 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2333 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2334 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2335 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2336 ce
->ce_file
= mh_xstrdup(cachefile
);
2340 admonish (cachefile
, "unable to fopen for reading");
2343 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2347 *file
= ce
->ce_file
;
2348 *fd
= fileno (ce
->ce_fp
);
2359 return init_encoding (ct
, openFile
);
2364 openFile (CT ct
, char **file
)
2367 char cachefile
[BUFSIZ
];
2368 struct exbody
*e
= ct
->c_ctexbody
;
2369 CE ce
= &ct
->c_cefile
;
2371 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2383 content_error (NULL
, ct
, "missing name parameter");
2387 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2390 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2391 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2395 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2396 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2397 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2401 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2402 if ((fp
= fopen (cachefile
, "w"))) {
2404 char buffer
[BUFSIZ
];
2405 FILE *gp
= ce
->ce_fp
;
2407 fseek (gp
, 0L, SEEK_SET
);
2409 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2411 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2412 advise ("openFile", "fwrite");
2417 admonish (ce
->ce_file
, "error reading");
2418 (void) m_unlink (cachefile
);
2419 } else if (ferror (fp
)) {
2420 admonish (cachefile
, "error writing");
2421 (void) m_unlink (cachefile
);
2428 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2429 *file
= ce
->ce_file
;
2430 return fileno (ce
->ce_fp
);
2440 return init_encoding (ct
, openFTP
);
2445 openFTP (CT ct
, char **file
)
2451 char *bp
, *ftp
, *user
, *pass
;
2452 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2454 CE ce
= &ct
->c_cefile
;
2455 static char *username
= NULL
;
2456 static char *password
= NULL
;
2460 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2466 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2477 if (!e
->eb_name
|| !e
->eb_site
) {
2478 content_error (NULL
, ct
, "missing %s parameter",
2479 e
->eb_name
? "site": "name");
2483 /* Get the buffer ready to go */
2485 buflen
= sizeof(buffer
);
2488 * Construct the query message for user
2490 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2496 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2502 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2503 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2508 if (e
->eb_size
> 0) {
2509 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2514 snprintf (bp
, buflen
, "? ");
2517 * Now, check the answer
2519 if (!read_yes_or_no_if_tty (buffer
))
2524 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2528 ruserpass (e
->eb_site
, &username
, &password
, 0);
2533 ce
->ce_unlink
= (*file
== NULL
);
2535 cachefile
[0] = '\0';
2536 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2537 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2538 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2539 if (*file
== NULL
) {
2546 ce
->ce_file
= mh_xstrdup(*file
);
2548 ce
->ce_file
= mh_xstrdup(cachefile
);
2551 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2552 die("unable to create temporary file in %s",
2555 ce
->ce_file
= mh_xstrdup(tempfile
);
2558 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2559 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2568 vec
[vecp
++] = r1bindex (ftp
, '/');
2569 vec
[vecp
++] = e
->eb_site
;
2572 vec
[vecp
++] = e
->eb_dir
;
2573 vec
[vecp
++] = e
->eb_name
;
2574 vec
[vecp
++] = ce
->ce_file
,
2575 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2576 ? "ascii" : "binary";
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
);
2629 } else if (ferror (fp
)) {
2630 admonish (cachefile
, "error writing");
2631 (void) m_unlink (cachefile
);
2639 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2640 *file
= ce
->ce_file
;
2641 return fileno (ce
->ce_fp
);
2652 return init_encoding (ct
, openMail
);
2657 openMail (CT ct
, char **file
)
2659 int child_id
, fd
, vecp
;
2661 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2662 struct exbody
*e
= ct
->c_ctexbody
;
2663 CE ce
= &ct
->c_cefile
;
2665 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2676 if (!e
->eb_server
) {
2677 content_error (NULL
, ct
, "missing server parameter");
2681 /* Get buffer ready to go */
2683 buflen
= sizeof(buffer
);
2685 /* Now, construct query message */
2686 snprintf (bp
, buflen
, "Retrieve content");
2692 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2698 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2700 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2702 /* Now, check answer */
2703 if (!read_yes_or_no_if_tty (buffer
))
2707 vec
[vecp
++] = r1bindex (mailproc
, '/');
2708 vec
[vecp
++] = e
->eb_server
;
2709 vec
[vecp
++] = "-subject";
2710 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2711 vec
[vecp
++] = "-body";
2712 vec
[vecp
++] = e
->eb_body
;
2718 advise ("fork", "unable to");
2722 execvp (mailproc
, vec
);
2723 fprintf (stderr
, "unable to exec ");
2729 if (pidXwait (child_id
, NULL
) == OK
)
2730 inform("request sent");
2734 if (*file
== NULL
) {
2736 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2737 die("unable to create temporary file in %s",
2740 ce
->ce_file
= mh_xstrdup(tempfile
);
2743 ce
->ce_file
= mh_xstrdup(*file
);
2747 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2748 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2752 /* showproc is for mhshow and mhstore, though mhlist -debug
2753 * prints it, too. */
2754 free(ct
->c_showproc
);
2755 ct
->c_showproc
= mh_xstrdup("true");
2757 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2758 *file
= ce
->ce_file
;
2759 return fileno (ce
->ce_fp
);
2770 return init_encoding (ct
, openURL
);
2775 openURL (CT ct
, char **file
)
2777 struct exbody
*e
= ct
->c_ctexbody
;
2778 CE ce
= &ct
->c_cefile
;
2779 char *urlprog
, *program
;
2780 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2784 struct msgs_array args
= { 0, 0, NULL
};
2787 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2791 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2795 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2807 content_error(NULL
, ct
, "missing url parameter");
2811 ce
->ce_unlink
= (*file
== NULL
);
2813 cachefile
[0] = '\0';
2815 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2816 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2817 if (*file
== NULL
) {
2824 ce
->ce_file
= mh_xstrdup(*file
);
2826 ce
->ce_file
= mh_xstrdup(cachefile
);
2829 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2830 die("unable to create temporary file in %s",
2833 ce
->ce_file
= mh_xstrdup(tempfile
);
2836 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2837 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2841 switch (child_id
= fork()) {
2843 adios ("fork", "unable to");
2847 argsplit_msgarg(&args
, urlprog
, &program
);
2848 app_msgarg(&args
, e
->eb_url
);
2849 app_msgarg(&args
, NULL
);
2850 dup2(fileno(ce
->ce_fp
), 1);
2851 close(fileno(ce
->ce_fp
));
2852 execvp(program
, args
.msgs
);
2853 fprintf(stderr
, "Unable to exec ");
2859 if (pidXwait(child_id
, NULL
)) {
2867 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2872 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2873 if ((fp
= fopen(cachefile
, "w"))) {
2875 FILE *gp
= ce
->ce_fp
;
2877 fseeko(gp
, 0, SEEK_SET
);
2879 while ((cc
= fread(buffer
, sizeof(*buffer
),
2880 sizeof(buffer
), gp
)) > 0)
2881 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2882 advise ("openURL", "fwrite");
2888 admonish(ce
->ce_file
, "error reading");
2889 (void) m_unlink (cachefile
);
2896 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2897 *file
= ce
->ce_file
;
2898 return fileno(ce
->ce_fp
);
2903 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2904 * has to be base64 decoded.
2907 readDigest (CT ct
, char *cp
)
2909 unsigned char *digest
;
2912 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2913 const size_t maxlen
= sizeof ct
->c_digest
;
2915 if (strlen ((char *) digest
) <= maxlen
) {
2916 memcpy (ct
->c_digest
, digest
, maxlen
);
2921 fprintf (stderr
, "MD5 digest=");
2922 for (i
= 0; i
< maxlen
; ++i
) {
2923 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2925 fprintf (stderr
, "\n");
2931 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2932 (int) strlen ((char *) digest
));
2942 /* Multipart parts might have content before the first subpart and/or
2943 after the last subpart that hasn't been stored anywhere else, so do
2946 get_leftover_mp_content (CT ct
, int before
/* or after */)
2948 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2950 bool found_boundary
= false;
2956 char *content
= NULL
;
2958 if (! m
) return NOTOK
;
2961 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2963 /* Isolate the beginning of this part to the beginning of the
2964 first subpart and save any content between them. */
2965 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2966 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2967 boundary
= concat ("--", m
->mp_start
, NULL
);
2969 struct part
*last_subpart
= NULL
;
2970 struct part
*subpart
;
2972 /* Go to the last subpart to get its end position. */
2973 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2974 last_subpart
= subpart
;
2977 if (last_subpart
== NULL
) return NOTOK
;
2979 /* Isolate the end of the last subpart to the end of this part
2980 and save any content between them. */
2981 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2982 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2983 boundary
= concat ("--", m
->mp_stop
, NULL
);
2986 /* Back up by 1 to pick up the newline. */
2987 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2989 /* Don't look beyond beginning of first subpart (before) or
2990 next part (after). */
2991 if (read
> max
) bufp
[read
-max
] = '\0';
2994 if (! strcmp (bufp
, boundary
)) {
2995 found_boundary
= true;
2998 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2999 found_boundary
= true;
3004 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3006 char *old_content
= content
;
3007 content
= concat (content
, bufp
, NULL
);
3011 ? concat ("\n", bufp
, NULL
)
3012 : concat (bufp
, NULL
);
3017 if (found_boundary
|| read
> max
) break;
3019 if (read
> max
) break;
3023 /* Skip the newline if that's all there is. */
3027 /* Remove trailing newline, except at EOF. */
3028 if ((before
|| ! feof (ct
->c_fp
)) &&
3029 (cp
= content
+ strlen (content
)) > content
&&
3034 if (strlen (content
) > 1) {
3036 m
->mp_content_before
= content
;
3038 m
->mp_content_after
= content
;
3053 ct_type_str (int type
)
3056 case CT_APPLICATION
:
3057 return "application";
3073 return "unknown_type";
3079 ct_subtype_str (int type
, int subtype
)
3082 case CT_APPLICATION
:
3084 case APPLICATION_OCTETS
:
3086 case APPLICATION_POSTSCRIPT
:
3087 return "postscript";
3089 return "unknown_app_subtype";
3093 case MESSAGE_RFC822
:
3095 case MESSAGE_PARTIAL
:
3097 case MESSAGE_EXTERNAL
:
3100 return "unknown_msg_subtype";
3106 case MULTI_ALTERNATE
:
3107 return "alternative";
3110 case MULTI_PARALLEL
:
3115 return "unknown_multipart_subtype";
3126 return "unknown_text_subtype";
3129 return "unknown_type";
3135 ct_str_type (const char *type
)
3137 struct str2init
*s2i
;
3139 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3140 if (! strcasecmp (type
, s2i
->si_key
)) {
3144 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3153 ct_str_subtype (int type
, const char *subtype
)
3158 case CT_APPLICATION
:
3159 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3160 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3164 return kv
->kv_value
;
3166 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3167 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3171 return kv
->kv_value
;
3173 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3174 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3178 return kv
->kv_value
;
3180 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3181 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3185 return kv
->kv_value
;
3192 /* Find the content type and InitFunc for the CT. */
3193 const struct str2init
*
3194 get_ct_init (int type
)
3196 const struct str2init
*sp
;
3198 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3199 if (type
== sp
->si_val
) {
3208 ce_str (int encoding
)
3214 return "quoted-printable";
3230 /* Find the content type and InitFunc for the content encoding method. */
3231 const struct str2init
*
3232 get_ce_method (const char *method
)
3234 struct str2init
*sp
;
3236 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3237 if (! strcasecmp (method
, sp
->si_key
)) {
3246 * Parse a series of MIME attributes (or parameters) given a header as
3249 * Arguments include:
3251 * filename - Name of input file (for error messages)
3252 * fieldname - Name of field being processed
3253 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3254 * Updated to point to end of attributes when finished.
3255 * param_head - Pointer to head of parameter list
3256 * param_tail - Pointer to tail of parameter list
3257 * commentp - Pointer to header comment pointer (may be NULL)
3259 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3260 * DONE to indicate a benign error (minor parsing error, but the program
3265 parse_header_attrs (const char *filename
, const char *fieldname
,
3266 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3269 char *cp
= *header_attrp
;
3275 struct sectlist
*next
;
3281 struct sectlist
*sechead
;
3282 struct parmlist
*next
;
3283 } *pp
, *pp2
, *phead
= NULL
;
3285 while (*cp
== ';') {
3286 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3287 bool encoded
= false;
3288 bool partial
= false;
3289 int len
= 0, index
= 0;
3292 while (isspace ((unsigned char) *cp
))
3296 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3301 if (! suppress_extraneous_trailing_semicolon_warning
) {
3302 inform("extraneous trailing ';' in message %s's %s: "
3303 "parameter list", filename
, fieldname
);
3308 /* down case the attribute name */
3309 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3310 *dp
= tolower ((unsigned char) *dp
);
3312 for (up
= dp
; isspace ((unsigned char) *dp
);)
3314 if (dp
== cp
|| *dp
!= '=') {
3315 inform("invalid parameter in message %s's %s: field\n"
3316 " parameter %s (error detected at offset %ld)",
3317 filename
, fieldname
, cp
, (long)(dp
- cp
));
3322 * To handle RFC 2231, we have to deal with the following extensions:
3324 * name*=encoded-value
3325 * name*<N>=part-N-of-a-parameter-value
3326 * name*<N>*=encoded-part-N-of-a-parameter-value
3329 * If there's a * right before the equal sign, it's encoded.
3330 * If there's a * and one or more digits, then it's section N.
3332 * Remember we can have one or the other, or both. cp points to
3333 * beginning of name, up points past the last character in the
3337 for (vp
= cp
; vp
< up
; vp
++) {
3338 if (*vp
== '*' && vp
< up
- 1) {
3342 if (*vp
== '*' && vp
== up
- 1) {
3344 } else if (partial
) {
3345 if (isdigit((unsigned char) *vp
))
3346 index
= *vp
- '0' + index
* 10;
3348 inform("invalid parameter index in message %s's %s: field"
3349 "\n (parameter %s)", filename
, fieldname
, cp
);
3358 * Break out the parameter name and value sections and allocate
3362 nameptr
= mh_xmalloc(len
+ 1);
3363 strncpy(nameptr
, cp
, len
);
3364 nameptr
[len
] = '\0';
3366 for (dp
++; isspace ((unsigned char) *dp
);)
3371 * Single quotes delimit the character set and language tag.
3372 * They are required on the first section (or a complete
3377 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3383 charset
= mh_xmalloc(len
+ 1);
3384 strncpy(charset
, dp
, len
);
3385 charset
[len
] = '\0';
3391 inform("missing charset in message %s's %s: field\n"
3392 " (parameter %s)", filename
, fieldname
, nameptr
);
3398 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3405 lang
= mh_xmalloc(len
+ 1);
3406 strncpy(lang
, dp
, len
);
3413 inform("missing language tag in message %s's %s: field\n"
3414 " (parameter %s)", filename
, fieldname
, nameptr
);
3424 * At this point vp should be pointing at the beginning
3425 * of the encoded value/section. Continue until we reach
3426 * the end or get whitespace. But first, calculate the
3427 * length so we can allocate the correct buffer size.
3430 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3432 if (*(vp
+ 1) == '\0' ||
3433 !isxdigit((unsigned char) *(vp
+ 1)) ||
3434 *(vp
+ 2) == '\0' ||
3435 !isxdigit((unsigned char) *(vp
+ 2))) {
3436 inform("invalid encoded sequence in message %s's %s: field\n"
3437 " (parameter %s)", filename
, fieldname
, nameptr
);
3448 up
= valptr
= mh_xmalloc(len
+ 1);
3450 for (vp
= dp
; istoken(*vp
); vp
++) {
3452 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3463 * A "normal" string. If it's got a leading quote, then we
3464 * strip the quotes out. Otherwise go until we reach the end
3465 * or get whitespace. Note we scan it twice; once to get the
3466 * length, then the second time copies it into the destination
3473 for (cp
= dp
+ 1;;) {
3477 inform("invalid quoted-string in message %s's %s: field\n"
3478 " (parameter %s)", filename
, fieldname
, nameptr
);
3498 for (cp
= dp
; istoken (*cp
); cp
++) {
3503 valptr
= mh_xmalloc(len
+ 1);
3507 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3515 strncpy(valptr
, cp
= dp
, len
);
3523 * If 'partial' is set, we don't allocate a parameter now. We
3524 * put it on the parameter linked list to be reassembled later.
3526 * "phead" points to a list of all parameters we need to reassemble.
3527 * Each parameter has a list of sections. We insert the sections in
3532 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3533 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3548 * Insert this into the section linked list
3556 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3557 sp
->next
= pp
->sechead
;
3560 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3561 if (sp2
->index
== sp
->index
) {
3562 inform("duplicate index (%d) in message %s's %s: field"
3563 "\n (parameter %s)", sp
->index
, filename
,
3564 fieldname
, nameptr
);
3567 if (sp2
->index
< sp
->index
&&
3568 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3569 sp
->next
= sp2
->next
;
3576 inform("Internal error: cannot insert partial param "
3577 "in message %s's %s: field\n (parameter %s)",
3578 filename
, fieldname
, nameptr
);
3584 * Save our charset and lang tags.
3587 if (index
== 0 && encoded
) {
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 message "
3619 "%s's %s: field\n (parameter %s)", pindex
- 1,
3620 filename
, fieldname
, pp
->name
);
3626 p
= q
= mh_xmalloc(tlen
+ 1);
3627 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3628 memcpy(q
, sp
->value
, sp
->len
);
3638 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3639 pm
->pm_charset
= pp
->charset
;
3640 pm
->pm_lang
= pp
->lang
;
3651 * Return the charset for a particular content type.
3655 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 die("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
4146 get_param_value(PM pm
, char replace
)
4148 static char buffer
[4096]; /* I hope no parameters are larger */
4149 size_t bufsize
= sizeof(buffer
);
4154 ICONV_CONST
char *p
;
4155 #else /* HAVE_ICONV */
4157 #endif /* HAVE_ICONV */
4162 * If we don't have a character set indicated, it's assumed to be
4163 * US-ASCII. If it matches our character set, we don't need to convert
4167 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4168 strlen(pm
->pm_charset
))) {
4169 return pm
->pm_value
;
4173 * In this case, we need to convert. If we have iconv support, use
4174 * that. Otherwise, go through and simply replace every non-ASCII
4175 * character with the substitution character.
4180 bufsize
= sizeof(buffer
);
4181 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4183 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4184 if (cd
== (iconv_t
) -1) {
4188 inbytes
= strlen(pm
->pm_value
);
4192 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4193 if (errno
!= EILSEQ
) {
4198 * Reset shift state, substitute our character,
4199 * try to restart conversion.
4202 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4215 for (++p
, --inbytes
;
4216 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4235 #endif /* HAVE_ICONV */
4238 * Take everything non-ASCII and substitute the replacement character
4242 bufsize
= sizeof(buffer
);
4243 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4244 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))