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/fmt_rfc2047.h"
11 #include "sbr/check_charset.h"
12 #include "sbr/getcpy.h"
13 #include "sbr/context_find.h"
14 #include "sbr/pidstatus.h"
15 #include "sbr/arglist.h"
16 #include "sbr/error.h"
22 #include "h/mhparse.h"
25 #include "h/mhcachesbr.h"
26 #include "sbr/m_mktemp.h"
30 #endif /* HAVE_ICONV */
31 #include "sbr/base64.h"
36 int checksw
= 0; /* check Content-MD5 field */
39 * These are for mhfixmsg to:
40 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
42 * 2) Suppress the warning about bogus multipart content, and report it.
43 * 3) Suppress the warning about extraneous trailing ';' in header parameter
46 bool skip_mp_cte_check
;
47 bool suppress_bogus_mp_content_warning
;
48 bool bogus_mp_content
;
49 bool suppress_extraneous_trailing_semicolon_warning
;
52 * By default, suppress warning about multiple MIME-Version header fields.
54 bool suppress_multiple_mime_version_warning
= true;
56 /* list of preferred type/subtype pairs, for -prefer */
57 mime_type_subtype mime_preference
[NPREFS
];
62 * Structures for TEXT messages
64 struct k2v SubText
[] = {
65 { "plain", TEXT_PLAIN
},
66 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
67 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
68 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
71 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
74 * Structures for MULTIPART messages
76 struct k2v SubMultiPart
[] = {
77 { "mixed", MULTI_MIXED
},
78 { "alternative", MULTI_ALTERNATE
},
79 { "digest", MULTI_DIGEST
},
80 { "parallel", MULTI_PARALLEL
},
81 { "related", MULTI_RELATED
},
82 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
86 * Structures for MESSAGE messages
88 struct k2v SubMessage
[] = {
89 { "rfc822", MESSAGE_RFC822
},
90 { "partial", MESSAGE_PARTIAL
},
91 { "external-body", MESSAGE_EXTERNAL
},
92 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
96 * Structure for APPLICATION messages
98 struct k2v SubApplication
[] = {
99 { "octet-stream", APPLICATION_OCTETS
},
100 { "postscript", APPLICATION_POSTSCRIPT
},
101 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
105 * Mapping of names of CTE types in mhbuild directives
107 static struct k2v EncodingType
[] = {
110 { "q-p", CE_QUOTED
},
111 { "quoted-printable", CE_QUOTED
},
112 { "b64", CE_BASE64
},
113 { "base64", CE_BASE64
},
121 static CT
get_content (FILE *, char *, int);
122 static int get_comment (const char *, const char *, char **, char **);
124 static int InitGeneric (CT
);
125 static int InitText (CT
);
126 static int InitMultiPart (CT
);
127 static void reverse_parts (CT
);
128 static void prefer_parts(CT ct
);
129 static int InitMessage (CT
);
130 static int InitApplication (CT
);
131 static int init_encoding (CT
, OpenCEFunc
);
132 static unsigned long size_encoding (CT
);
133 static int InitBase64 (CT
);
134 static int openBase64 (CT
, char **);
135 static int InitQuoted (CT
);
136 static int openQuoted (CT
, char **);
137 static int Init7Bit (CT
);
138 static int openExternal (CT
, CT
, CE
, char **, int *);
139 static int InitFile (CT
);
140 static int openFile (CT
, char **);
141 static int InitFTP (CT
);
142 static int openFTP (CT
, char **);
143 static int InitMail (CT
);
144 static int openMail (CT
, char **);
145 static int readDigest (CT
, char *);
146 static int get_leftover_mp_content (CT
, int);
147 static int InitURL (CT
);
148 static int openURL (CT
, char **);
149 static int parse_header_attrs (const char *, const char *, char **, PM
*,
151 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
152 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
153 static int get_dispo (char *, CT
, int);
155 struct str2init str2cts
[] = {
156 { "application", CT_APPLICATION
, InitApplication
},
157 { "audio", CT_AUDIO
, InitGeneric
},
158 { "image", CT_IMAGE
, InitGeneric
},
159 { "message", CT_MESSAGE
, InitMessage
},
160 { "multipart", CT_MULTIPART
, InitMultiPart
},
161 { "text", CT_TEXT
, InitText
},
162 { "video", CT_VIDEO
, InitGeneric
},
163 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
164 { NULL
, CT_UNKNOWN
, NULL
},
167 struct str2init str2ces
[] = {
168 { "base64", CE_BASE64
, InitBase64
},
169 { "quoted-printable", CE_QUOTED
, InitQuoted
},
170 { "8bit", CE_8BIT
, Init7Bit
},
171 { "7bit", CE_7BIT
, Init7Bit
},
172 { "binary", CE_BINARY
, Init7Bit
},
173 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
174 { NULL
, CE_UNKNOWN
, NULL
},
178 * NOTE WELL: si_key MUST NOT have value of NOTOK
180 * si_val is 1 if access method is anonymous.
182 struct str2init str2methods
[] = {
183 { "afs", 1, InitFile
},
184 { "anon-ftp", 1, InitFTP
},
185 { "ftp", 0, InitFTP
},
186 { "local-file", 0, InitFile
},
187 { "mail-server", 0, InitMail
},
188 { "url", 0, InitURL
},
194 * Main entry point for parsing a MIME message or file.
195 * It returns the Content structure for the top level
196 * entity in the file.
200 parse_mime (char *file
)
209 bogus_mp_content
= false;
212 * Check if file is actually standard input
214 if ((is_stdin
= !(strcmp (file
, "-")))) {
215 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
217 advise("mhparse", "unable to create temporary file in %s",
221 file
= mh_xstrdup(tfile
);
223 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
224 if (fwrite(buffer
, 1, n
, fp
) != n
) {
225 (void) m_unlink (file
);
226 advise (file
, "error copying to temporary file");
232 if (ferror (stdin
)) {
233 (void) m_unlink (file
);
234 advise ("stdin", "error reading");
238 (void) m_unlink (file
);
239 advise (file
, "error writing");
242 fseek (fp
, 0L, SEEK_SET
);
243 } else if (stat (file
, &statbuf
) == NOTOK
) {
244 advise (file
, "unable to stat");
246 } else if (S_ISDIR(statbuf
.st_mode
)) {
247 /* Don't try to parse a directory. */
248 inform("%s is a directory", file
);
250 } else if ((fp
= fopen (file
, "r")) == NULL
) {
251 advise (file
, "unable to read");
255 if (!(ct
= get_content (fp
, file
, 1))) {
257 (void) m_unlink (file
);
258 inform("unable to decode %s", file
);
263 ct
->c_unlink
= 1; /* temp file to remove */
267 if (ct
->c_end
== 0L) {
268 fseek (fp
, 0L, SEEK_END
);
269 ct
->c_end
= ftell (fp
);
272 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
284 * Main routine for reading/parsing the headers
285 * of a message content.
287 * toplevel = 1 # we are at the top level of the message
288 * toplevel = 0 # we are inside message type or multipart type
289 * # other than multipart/digest
290 * toplevel = -1 # we are inside multipart/digest
291 * NB: on failure we will fclose(in)!
295 get_content (FILE *in
, char *file
, int toplevel
)
298 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
302 m_getfld_state_t gstate
;
304 /* allocate the content structure */
307 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
308 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
311 * Parse the header fields for this
312 * content into a linked list.
314 gstate
= m_getfld_state_init(in
);
315 m_getfld_track_filepos2(&gstate
);
316 for (compnum
= 1;;) {
317 int bufsz
= sizeof buf
;
318 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
323 /* get copies of the buffers */
324 np
= mh_xstrdup(name
);
325 vp
= mh_xstrdup(buf
);
327 /* if necessary, get rest of field */
328 while (state
== FLDPLUS
) {
330 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
331 vp
= add (buf
, vp
); /* add to previous value */
334 /* Now add the header data to the list */
335 add_header (ct
, np
, vp
);
337 /* continue, to see if this isn't the last header field */
338 ct
->c_begin
= ftell (in
) + 1;
342 /* There are two cases. The unusual one is when there is no
343 * blank line between the headers and the body. This is
344 * indicated by the name of the header starting with `:'.
346 * For both cases, normal first, `1' is the desired c_begin
347 * file position for the start of the body, and `2' is the
348 * file position when buf is returned.
350 * f o o : b a r \n \n b o d y \n bufsz = 6
352 * f o o : b a r \n b o d y \n bufsz = 4
355 * For the normal case, bufsz includes the
356 * header-terminating `\n', even though it is not in buf,
357 * but bufsz isn't affected when it's missing in the unusual
359 if (name
[0] == ':') {
360 ct
->c_begin
= ftell(in
) - bufsz
;
362 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
367 ct
->c_begin
= ftell (in
);
372 die("message format error in component #%d", compnum
);
375 die("getfld() returned %d", state
);
378 /* break out of the loop */
381 m_getfld_state_destroy (&gstate
);
384 * Read the content headers. We will parse the
385 * MIME related header fields into their various
386 * structures and set internal flags related to
387 * content type/subtype, etc.
390 hp
= ct
->c_first_hf
; /* start at first header field */
392 /* Get MIME-Version field */
393 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
398 vrsn
= mh_xstrdup(FENDNULL(hp
->value
));
400 /* Now, cleanup this field */
403 while (isspace ((unsigned char) *cp
))
405 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
407 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
408 if (!isspace ((unsigned char) *dp
))
412 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
415 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
418 for (dp
= cp
; istoken (*dp
); dp
++)
422 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
425 inform("message %s has unknown value for %s: field (%s), continuing...",
426 ct
->c_file
, VRSN_FIELD
, cp
);
431 if (! suppress_multiple_mime_version_warning
)
432 inform("message %s has multiple %s: fields",
433 ct
->c_file
, VRSN_FIELD
);
437 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
438 /* Get Content-Type field */
439 struct str2init
*s2i
;
440 CI ci
= &ct
->c_ctinfo
;
442 /* Check if we've already seen a Content-Type header */
444 inform("message %s has multiple %s: fields",
445 ct
->c_file
, TYPE_FIELD
);
449 /* Parse the Content-Type field */
450 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
454 * Set the Init function and the internal
455 * flag for this content type.
457 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
458 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
460 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
462 ct
->c_type
= s2i
->si_val
;
463 ct
->c_ctinitfnx
= s2i
->si_init
;
465 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
466 /* Get Content-Transfer-Encoding field */
468 struct str2init
*s2i
;
471 * Check if we've already seen the
472 * Content-Transfer-Encoding field
475 inform("message %s has multiple %s: fields",
476 ct
->c_file
, ENCODING_FIELD
);
480 /* get copy of this field */
481 ct
->c_celine
= cp
= mh_xstrdup(FENDNULL(hp
->value
));
483 while (isspace ((unsigned char) *cp
))
485 for (dp
= cp
; istoken (*dp
); dp
++)
491 * Find the internal flag and Init function
492 * for this transfer encoding.
494 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
495 if (!strcasecmp (cp
, s2i
->si_key
))
497 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
500 ct
->c_encoding
= s2i
->si_val
;
502 /* Call the Init function for this encoding */
503 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
506 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
507 /* Get Content-MD5 field */
513 if (ct
->c_digested
) {
514 inform("message %s has multiple %s: fields",
515 ct
->c_file
, MD5_FIELD
);
519 ep
= cp
= mh_xstrdup(FENDNULL(hp
->value
)); /* get a copy */
521 while (isspace ((unsigned char) *cp
))
523 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
525 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
526 if (!isspace ((unsigned char) *dp
))
530 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
533 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
538 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
546 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
547 /* Get Content-ID field */
548 ct
->c_id
= add (hp
->value
, ct
->c_id
);
550 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
551 /* Get Content-Description field */
552 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
554 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
555 /* Get Content-Disposition field */
556 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
561 hp
= hp
->next
; /* next header field */
565 * Check if we saw a Content-Type field.
566 * If not, then assign a default value for
567 * it, and the Init function.
571 * If we are inside a multipart/digest message,
572 * so default type is message/rfc822
575 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
577 ct
->c_type
= CT_MESSAGE
;
578 ct
->c_ctinitfnx
= InitMessage
;
581 * Else default type is text/plain
583 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
585 ct
->c_type
= CT_TEXT
;
586 ct
->c_ctinitfnx
= InitText
;
590 /* Use default Transfer-Encoding, if necessary */
592 ct
->c_encoding
= CE_7BIT
;
605 * small routine to add header field to list
609 add_header (CT ct
, char *name
, char *value
)
613 /* allocate header field structure */
616 /* link data into header structure */
621 /* link header structure into the list */
622 if (ct
->c_first_hf
== NULL
) {
623 ct
->c_first_hf
= hp
; /* this is the first */
626 ct
->c_last_hf
->next
= hp
; /* add it to the end */
635 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
636 * directives. Fills in the information of the CTinfo structure.
639 get_ctinfo (char *cp
, CT ct
, int magic
)
648 /* store copy of Content-Type line */
649 cp
= ct
->c_ctline
= mh_xstrdup(FENDNULL(cp
));
651 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
654 /* change newlines to spaces */
655 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
658 /* trim trailing spaces */
659 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
660 if (!isspace ((unsigned char) *dp
))
665 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
667 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
668 &ci
->ci_comment
) == NOTOK
)
671 for (dp
= cp
; istoken (*dp
); dp
++)
675 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
680 inform("invalid %s: field in message %s (empty type)",
681 TYPE_FIELD
, ct
->c_file
);
684 to_lower(ci
->ci_type
);
686 while (isspace ((unsigned char) *cp
))
689 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
690 &ci
->ci_comment
) == NOTOK
)
695 ci
->ci_subtype
= mh_xstrdup("");
700 while (isspace ((unsigned char) *cp
))
703 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
704 &ci
->ci_comment
) == NOTOK
)
707 for (dp
= cp
; istoken (*dp
); dp
++)
711 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
715 if (!*ci
->ci_subtype
) {
716 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
717 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
720 to_lower(ci
->ci_subtype
);
723 while (isspace ((unsigned char) *cp
))
726 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
727 &ci
->ci_comment
) == NOTOK
)
730 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
731 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
732 &ci
->ci_comment
)) != OK
) {
733 return status
== NOTOK
? NOTOK
: OK
;
737 * Get any <Content-Id> given in buffer
739 if (magic
&& *cp
== '<') {
742 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
743 inform("invalid ID in message %s", ct
->c_file
);
749 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
755 while (isspace ((unsigned char) *cp
))
760 * Get any [Content-Description] given in buffer.
762 if (magic
&& *cp
== '[') {
764 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
768 inform("invalid description in message %s", ct
->c_file
);
776 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
782 while (isspace ((unsigned char) *cp
))
787 * Get any {Content-Disposition} given in buffer.
789 if (magic
&& *cp
== '{') {
791 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
795 inform("invalid disposition in message %s", ct
->c_file
);
803 if (get_dispo(cp
, ct
, 1) != OK
)
809 while (isspace ((unsigned char) *cp
))
814 * Get any extension directives (right now just the content transfer
815 * encoding, but maybe others) that we care about.
818 if (magic
&& *cp
== '*') {
820 * See if it's a CTE we match on
825 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
829 inform("invalid null transfer encoding specification");
836 ct
->c_reqencoding
= CE_UNKNOWN
;
838 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
839 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
840 ct
->c_reqencoding
= kv
->kv_value
;
845 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
846 inform("invalid CTE specification: \"%s\"", dp
);
850 while (isspace ((unsigned char) *cp
))
855 * Check if anything is left over
859 ci
->ci_magic
= mh_xstrdup(cp
);
861 /* If there is a Content-Disposition header and it doesn't
862 have a *filename=, extract it from the magic contents.
863 The r1bindex call skips any leading directory
865 if (ct
->c_dispo_type
&&
866 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
867 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
868 r1bindex(ci
->ci_magic
, '/'), 0);
872 inform("extraneous information in message %s's %s: field\n"
873 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
881 * Parse out a Content-Disposition header. A lot of this is cribbed from
885 get_dispo (char *cp
, CT ct
, int buildflag
)
887 char *dp
, *dispoheader
;
892 * Save the whole copy of the Content-Disposition header, unless we're
893 * processing a mhbuild directive. A NULL c_dispo will be a flag to
894 * mhbuild that the disposition header needs to be generated at that
898 dispoheader
= cp
= mh_xstrdup(FENDNULL(cp
));
900 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
903 /* change newlines to spaces */
904 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
907 /* trim trailing spaces */
908 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
909 if (!isspace ((unsigned char) *dp
))
914 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
916 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
922 for (dp
= cp
; istoken (*dp
); dp
++)
926 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
930 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
933 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
934 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
936 if (status
== NOTOK
) {
941 inform("extraneous information in message %s's %s: field\n (%s)",
942 ct
->c_file
, DISPO_FIELD
, cp
);
948 ct
->c_dispo
= dispoheader
;
955 get_comment (const char *filename
, const char *fieldname
, char **ap
,
960 char c
, buffer
[BUFSIZ
], *dp
;
970 inform("invalid comment in message %s's %s: field",
971 filename
, fieldname
);
976 if ((c
= *cp
++) == '\0')
999 if ((dp
= *commentp
)) {
1000 *commentp
= concat (dp
, " ", buffer
, NULL
);
1003 *commentp
= mh_xstrdup(buffer
);
1007 while (isspace ((unsigned char) *cp
))
1018 * Handles content types audio, image, and video.
1019 * There's not much to do right here.
1027 return OK
; /* not much to do here */
1038 char buffer
[BUFSIZ
];
1043 CI ci
= &ct
->c_ctinfo
;
1045 /* check for missing subtype */
1046 if (!*ci
->ci_subtype
)
1047 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1050 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1052 /* allocate text character set structure */
1054 ct
->c_ctparams
= (void *) t
;
1056 /* scan for charset parameter */
1057 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1058 if (!strcasecmp (pm
->pm_name
, "charset"))
1061 /* check if content specified a character set */
1063 chset
= pm
->pm_value
;
1064 t
->tx_charset
= CHARSET_SPECIFIED
;
1066 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1070 * If we can not handle character set natively,
1071 * then check profile for string to modify the
1072 * terminal or display method.
1074 * termproc is for mhshow, though mhlist -debug prints it, too.
1076 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1077 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1078 if ((cp
= context_find (buffer
)))
1079 ct
->c_termproc
= mh_xstrdup(cp
);
1091 InitMultiPart (CT ct
)
1101 struct multipart
*m
;
1102 struct part
*part
, **next
;
1103 CI ci
= &ct
->c_ctinfo
;
1108 * The encoding for multipart messages must be either
1109 * 7bit, 8bit, or binary (per RFC 2045).
1111 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1112 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1113 /* Copy the Content-Transfer-Encoding header field body so we can
1114 remove any trailing whitespace and leading blanks from it. */
1115 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1117 bp
= cte
+ strlen (cte
) - 1;
1118 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1119 for (bp
= cte
; isblank((unsigned char)*bp
); ++bp
) continue;
1121 inform("\"%s/%s\" type in message %s must be encoded in\n"
1122 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1123 "mhfixmsg -fixcte can fix it, or\n"
1124 "manually edit the file and change the \"%s\"\n"
1125 "Content-Transfer-Encoding to one of those. For now, continuing...",
1126 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1133 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1136 * Check for "boundary" parameter, which is
1137 * required for multipart messages.
1140 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1141 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1147 /* complain if boundary parameter is missing */
1149 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1150 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1154 /* allocate primary structure for multipart info */
1156 ct
->c_ctparams
= (void *) m
;
1158 /* check if boundary parameter contains only whitespace characters */
1159 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1162 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1163 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1167 /* remove trailing whitespace from boundary parameter */
1168 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1169 if (!isspace ((unsigned char) *dp
))
1173 /* record boundary separators */
1174 m
->mp_start
= concat (bp
, "\n", NULL
);
1175 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1177 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1178 advise (ct
->c_file
, "unable to open for reading");
1182 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1184 next
= &m
->mp_parts
;
1188 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1193 if (bufp
[0] != '-' || bufp
[1] != '-')
1196 if (strcmp (bufp
+ 2, m
->mp_start
))
1201 next
= &part
->mp_next
;
1203 if (!(p
= get_content (fp
, ct
->c_file
,
1204 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1212 fseek (fp
, pos
, SEEK_SET
);
1215 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1219 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1220 if (p
->c_end
< p
->c_begin
)
1221 p
->c_begin
= p
->c_end
;
1226 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1231 if (! suppress_bogus_mp_content_warning
) {
1232 inform("bogus multipart content in message %s", ct
->c_file
);
1234 bogus_mp_content
= true;
1236 if (!inout
&& part
) {
1238 p
->c_end
= ct
->c_end
;
1240 if (p
->c_begin
>= p
->c_end
) {
1241 for (next
= &m
->mp_parts
; *next
!= part
;
1242 next
= &((*next
)->mp_next
))
1251 /* reverse the order of the parts for multipart/alternative */
1252 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1258 * label all subparts with part number, and
1259 * then initialize the content of the subpart.
1264 char partnam
[BUFSIZ
];
1267 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1268 pp
= partnam
+ strlen (partnam
);
1273 for (part
= m
->mp_parts
, partnum
= 1; part
;
1274 part
= part
->mp_next
, partnum
++) {
1277 sprintf (pp
, "%d", partnum
);
1278 p
->c_partno
= mh_xstrdup(partnam
);
1280 /* initialize the content of the subparts */
1281 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1290 get_leftover_mp_content (ct
, 1);
1291 get_leftover_mp_content (ct
, 0);
1301 * reverse the order of the parts of a multipart/alternative,
1302 * presumably to put the "most favored" alternative first, for
1303 * ease of choosing/displaying it later on. from a mail message on
1304 * nmh-workers, from kenh:
1305 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1306 * see code in mhn that did the same thing... According to the RCS
1307 * logs, that code was around from the initial checkin of mhn.c by
1308 * John Romine in 1992, which is as far back as we have."
1311 reverse_parts (CT ct
)
1313 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1317 /* Reverse the order of its parts by walking the mp_parts list
1318 and pushing each node to the front. */
1319 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1320 next
= part
->mp_next
;
1321 part
->mp_next
= m
->mp_parts
;
1327 move_preferred_part(CT ct
, mime_type_subtype
*pref
)
1329 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1330 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1334 /* move the matching part(s) to the head of the list: walk the
1335 * list of parts, move matching parts to a new list (maintaining
1336 * their order), and finally, concatenate the old list onto the
1343 head
->mp_next
= m
->mp_parts
;
1344 nhead
->mp_next
= NULL
;
1348 part
= head
->mp_next
;
1349 while (part
!= NULL
) {
1350 ci
= &part
->mp_part
->c_ctinfo
;
1351 if (!strcasecmp(ci
->ci_type
, pref
->type
) &&
1353 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1354 prev
->mp_next
= part
->mp_next
;
1355 part
->mp_next
= NULL
;
1356 ntail
->mp_next
= part
;
1358 part
= prev
->mp_next
;
1361 part
= prev
->mp_next
;
1364 ntail
->mp_next
= head
->mp_next
;
1365 m
->mp_parts
= nhead
->mp_next
;
1369 * move parts that match the user's preferences (-prefer) to the head
1370 * of the line. process preferences in reverse so first one given
1371 * ends up first in line
1377 for (i
= 0; i
< npreferred
; i
++)
1378 move_preferred_part(ct
, mime_preference
+ i
);
1383 /* parse_mime() arranges alternates in reverse (priority) order. This
1384 function can be used to reverse them back. This will put, for
1385 example, a text/plain part before a text/html part in a
1386 multipart/alternative part, for example, where it belongs. */
1388 reverse_alternative_parts (CT ct
)
1390 if (ct
->c_type
== CT_MULTIPART
) {
1391 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1394 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1398 /* And call recursively on each part of a multipart. */
1399 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1400 reverse_alternative_parts (part
->mp_part
);
1413 CI ci
= &ct
->c_ctinfo
;
1415 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1416 inform("\"%s/%s\" type in message %s should be encoded in "
1417 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1422 /* check for missing subtype */
1423 if (!*ci
->ci_subtype
)
1424 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1427 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1429 switch (ct
->c_subtype
) {
1430 case MESSAGE_RFC822
:
1433 case MESSAGE_PARTIAL
:
1439 ct
->c_ctparams
= (void *) p
;
1441 /* scan for parameters "id", "number", and "total" */
1442 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1443 if (!strcasecmp (pm
->pm_name
, "id")) {
1444 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1447 if (!strcasecmp (pm
->pm_name
, "number")) {
1448 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1449 || p
->pm_partno
< 1) {
1451 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1452 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1453 ct
->c_file
, TYPE_FIELD
);
1458 if (!strcasecmp (pm
->pm_name
, "total")) {
1459 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1468 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1469 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1470 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1476 case MESSAGE_EXTERNAL
:
1484 ct
->c_ctparams
= (void *) e
;
1487 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1488 advise (ct
->c_file
, "unable to open for reading");
1492 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1494 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1502 p
->c_ceopenfnx
= NULL
;
1503 if ((exresult
= params_external (ct
, 0)) != NOTOK
1504 && p
->c_ceopenfnx
== openMail
) {
1508 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1510 content_error (NULL
, ct
,
1511 "empty body for access-type=mail-server");
1515 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1516 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1518 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1520 adios ("failed", "fread");
1523 die("unexpected EOF from fread");
1526 bp
+= cc
, size
-= cc
;
1533 p
->c_end
= p
->c_begin
;
1538 if (exresult
== NOTOK
)
1540 if (e
->eb_flags
== NOTOK
)
1543 switch (p
->c_type
) {
1548 if (p
->c_subtype
!= MESSAGE_RFC822
)
1552 e
->eb_partno
= ct
->c_partno
;
1554 (*p
->c_ctinitfnx
) (p
);
1569 params_external (CT ct
, int composing
)
1572 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1573 CI ci
= &ct
->c_ctinfo
;
1575 ct
->c_ceopenfnx
= NULL
;
1576 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1577 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1578 struct str2init
*s2i
;
1579 CT p
= e
->eb_content
;
1581 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1582 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1585 e
->eb_access
= pm
->pm_value
;
1586 e
->eb_flags
= NOTOK
;
1587 p
->c_encoding
= CE_EXTERNAL
;
1590 e
->eb_access
= s2i
->si_key
;
1591 e
->eb_flags
= s2i
->si_val
;
1592 p
->c_encoding
= CE_EXTERNAL
;
1594 /* Call the Init function for this external type */
1595 if ((*s2i
->si_init
)(p
) == NOTOK
)
1599 if (!strcasecmp (pm
->pm_name
, "name")) {
1600 e
->eb_name
= pm
->pm_value
;
1603 if (!strcasecmp (pm
->pm_name
, "permission")) {
1604 e
->eb_permission
= pm
->pm_value
;
1607 if (!strcasecmp (pm
->pm_name
, "site")) {
1608 e
->eb_site
= pm
->pm_value
;
1611 if (!strcasecmp (pm
->pm_name
, "directory")) {
1612 e
->eb_dir
= pm
->pm_value
;
1615 if (!strcasecmp (pm
->pm_name
, "mode")) {
1616 e
->eb_mode
= pm
->pm_value
;
1619 if (!strcasecmp (pm
->pm_name
, "size")) {
1620 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1623 if (!strcasecmp (pm
->pm_name
, "server")) {
1624 e
->eb_server
= pm
->pm_value
;
1627 if (!strcasecmp (pm
->pm_name
, "subject")) {
1628 e
->eb_subject
= pm
->pm_value
;
1631 if (!strcasecmp (pm
->pm_name
, "url")) {
1633 * According to RFC 2017, we have to remove all whitespace from
1637 char *u
, *p
= pm
->pm_value
;
1638 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1640 for (; *p
!= '\0'; p
++) {
1641 if (! isspace((unsigned char) *p
))
1648 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1649 e
->eb_body
= getcpy (pm
->pm_value
);
1654 if (!e
->eb_access
) {
1655 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1656 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1669 InitApplication (CT ct
)
1671 CI ci
= &ct
->c_ctinfo
;
1674 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1681 * TRANSFER ENCODINGS
1685 init_encoding (CT ct
, OpenCEFunc openfnx
)
1687 ct
->c_ceopenfnx
= openfnx
;
1688 ct
->c_ceclosefnx
= close_encoding
;
1689 ct
->c_cesizefnx
= size_encoding
;
1696 close_encoding (CT ct
)
1698 CE ce
= &ct
->c_cefile
;
1707 static unsigned long
1708 size_encoding (CT ct
)
1713 CE ce
= &ct
->c_cefile
;
1716 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1717 return (long) st
.st_size
;
1720 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1721 return (long) st
.st_size
;
1725 if (ct
->c_encoding
== CE_EXTERNAL
)
1726 return ct
->c_end
- ct
->c_begin
;
1729 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1730 return ct
->c_end
- ct
->c_begin
;
1732 if (fstat (fd
, &st
) != NOTOK
)
1733 size
= (long) st
.st_size
;
1737 (*ct
->c_ceclosefnx
) (ct
);
1749 return init_encoding (ct
, openBase64
);
1754 openBase64 (CT ct
, char **file
)
1758 bool own_ct_fp
= false;
1759 char *cp
, *buffer
= NULL
;
1760 /* sbeck -- handle suffixes */
1762 CE ce
= &ct
->c_cefile
;
1763 unsigned char *decoded
;
1765 unsigned char digest
[16];
1768 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1773 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1774 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1780 if (*file
== NULL
) {
1783 ce
->ce_file
= mh_xstrdup(*file
);
1787 /* sbeck@cise.ufl.edu -- handle suffixes */
1789 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1790 if (ce
->ce_unlink
) {
1791 /* Create temporary file with filename extension. */
1792 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1793 die("unable to create temporary file in %s",
1797 ce
->ce_file
= add (cp
, ce
->ce_file
);
1799 } else if (*file
== NULL
) {
1801 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1802 die("unable to create temporary file in %s",
1805 ce
->ce_file
= mh_xstrdup(tempfile
);
1808 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1809 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1813 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1814 die("internal error(1)");
1816 buffer
= mh_xmalloc (len
+ 1);
1819 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1820 content_error (ct
->c_file
, ct
, "unable to open for reading");
1826 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1829 switch (cc
= read (fd
, cp
, len
)) {
1831 content_error (ct
->c_file
, ct
, "error reading from");
1835 content_error (NULL
, ct
, "premature eof");
1846 /* decodeBase64() requires null-terminated input. */
1849 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1850 ct
->c_digested
? digest
: NULL
) != OK
)
1855 unsigned char *decoded_p
= decoded
;
1856 for (i
= 0; i
< decoded_len
; ++i
) {
1857 putc (*decoded_p
++, ce
->ce_fp
);
1860 if (ferror (ce
->ce_fp
)) {
1861 content_error (ce
->ce_file
, ct
, "error writing to");
1865 if (ct
->c_digested
) {
1866 if (memcmp(digest
, ct
->c_digest
,
1868 content_error (NULL
, ct
,
1869 "content integrity suspect (digest mismatch) -- continuing");
1872 fprintf (stderr
, "content integrity confirmed\n");
1878 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1880 if (fflush (ce
->ce_fp
)) {
1881 content_error (ce
->ce_file
, ct
, "error writing to");
1885 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1888 *file
= ce
->ce_file
;
1894 return fileno (ce
->ce_fp
);
1901 free_encoding (ct
, 0);
1911 static char hex2nib
[0x80] = {
1912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1913 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1914 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1915 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1916 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1917 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1918 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1919 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1920 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1921 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1922 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1923 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1924 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1925 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1926 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1927 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1934 return init_encoding (ct
, openQuoted
);
1939 openQuoted (CT ct
, char **file
)
1941 int cc
, digested
, len
, quoted
;
1942 bool own_ct_fp
= false;
1948 CE ce
= &ct
->c_cefile
;
1949 /* sbeck -- handle suffixes */
1954 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1959 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1960 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1966 if (*file
== NULL
) {
1969 ce
->ce_file
= mh_xstrdup(*file
);
1973 /* sbeck@cise.ufl.edu -- handle suffixes */
1975 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1976 if (ce
->ce_unlink
) {
1977 /* Create temporary file with filename extension. */
1978 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1979 die("unable to create temporary file in %s",
1983 ce
->ce_file
= add (cp
, ce
->ce_file
);
1985 } else if (*file
== NULL
) {
1987 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1988 die("unable to create temporary file in %s",
1991 ce
->ce_file
= mh_xstrdup(tempfile
);
1994 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1995 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1999 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2000 die("internal error(2)");
2003 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2004 content_error (ct
->c_file
, ct
, "unable to open for reading");
2010 if ((digested
= ct
->c_digested
))
2011 MD5Init (&mdContext
);
2015 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2017 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2018 content_error (NULL
, ct
, "premature eof");
2022 if ((cc
= gotlen
) > len
)
2026 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2027 if (!isspace ((unsigned char) *ep
))
2032 for (; cp
< ep
; cp
++) {
2034 /* in an escape sequence */
2036 /* at byte 1 of an escape sequence */
2037 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2038 /* next is byte 2 */
2041 /* at byte 2 of an escape sequence */
2043 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2044 putc (mask
, ce
->ce_fp
);
2046 MD5Update (&mdContext
, &mask
, 1);
2047 if (ferror (ce
->ce_fp
)) {
2048 content_error (ce
->ce_file
, ct
, "error writing to");
2051 /* finished escape sequence; next may be literal or a new
2052 * escape sequence */
2055 /* on to next byte */
2059 /* not in an escape sequence */
2061 /* starting an escape sequence, or invalid '='? */
2062 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2063 /* "=\n" soft line break, eat the \n */
2067 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2068 /* We don't have 2 bytes left, so this is an invalid
2069 * escape sequence; just show the raw bytes (below). */
2070 } else if (isxdigit ((unsigned char) cp
[1]) &&
2071 isxdigit ((unsigned char) cp
[2])) {
2072 /* Next 2 bytes are hex digits, making this a valid escape
2073 * sequence; let's decode it (above). */
2077 /* One or both of the next 2 is out of range, making this
2078 * an invalid escape sequence; just show the raw bytes
2082 /* Just show the raw byte. */
2083 putc (*cp
, ce
->ce_fp
);
2086 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2088 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2091 if (ferror (ce
->ce_fp
)) {
2092 content_error (ce
->ce_file
, ct
, "error writing to");
2098 content_error (NULL
, ct
,
2099 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2103 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2105 if (fflush (ce
->ce_fp
)) {
2106 content_error (ce
->ce_file
, ct
, "error writing to");
2111 unsigned char digest
[16];
2113 MD5Final (digest
, &mdContext
);
2114 if (memcmp(digest
, ct
->c_digest
,
2116 content_error (NULL
, ct
,
2117 "content integrity suspect (digest mismatch) -- continuing");
2119 fprintf (stderr
, "content integrity confirmed\n");
2122 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2125 *file
= ce
->ce_file
;
2131 return fileno (ce
->ce_fp
);
2134 free_encoding (ct
, 0);
2151 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2154 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2160 open7Bit (CT ct
, char **file
)
2163 bool own_ct_fp
= false;
2164 char buffer
[BUFSIZ
];
2165 /* sbeck -- handle suffixes */
2168 CE ce
= &ct
->c_cefile
;
2171 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2176 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2177 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2183 if (*file
== NULL
) {
2186 ce
->ce_file
= mh_xstrdup(*file
);
2190 /* sbeck@cise.ufl.edu -- handle suffixes */
2192 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2193 if (ce
->ce_unlink
) {
2194 /* Create temporary file with filename extension. */
2195 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2196 die("unable to create temporary file in %s",
2200 ce
->ce_file
= add (cp
, ce
->ce_file
);
2202 } else if (*file
== NULL
) {
2204 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2205 die("unable to create temporary file in %s",
2208 ce
->ce_file
= mh_xstrdup(tempfile
);
2211 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2212 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2216 if (ct
->c_type
== CT_MULTIPART
) {
2217 CI ci
= &ct
->c_ctinfo
;
2221 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2222 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2223 + 1 + strlen (ci
->ci_subtype
);
2224 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2227 fputs (buffer
, ce
->ce_fp
);
2231 if (ci
->ci_comment
) {
2232 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2233 fputs ("\n\t", ce
->ce_fp
);
2237 putc (' ', ce
->ce_fp
);
2240 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2243 fprintf (ce
->ce_fp
, "\n");
2245 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2247 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2249 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2250 fprintf (ce
->ce_fp
, "\n");
2253 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2254 die("internal error(3)");
2257 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2258 content_error (ct
->c_file
, ct
, "unable to open for reading");
2264 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2266 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2268 content_error (ct
->c_file
, ct
, "error reading from");
2272 content_error (NULL
, ct
, "premature eof");
2280 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2281 advise ("open7Bit", "fwrite");
2283 if (ferror (ce
->ce_fp
)) {
2284 content_error (ce
->ce_file
, ct
, "error writing to");
2289 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2291 if (fflush (ce
->ce_fp
)) {
2292 content_error (ce
->ce_file
, ct
, "error writing to");
2296 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2299 *file
= ce
->ce_file
;
2304 return fileno (ce
->ce_fp
);
2307 free_encoding (ct
, 0);
2321 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2323 char cachefile
[BUFSIZ
];
2326 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2331 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2332 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2338 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2339 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2340 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2341 ce
->ce_file
= mh_xstrdup(cachefile
);
2345 admonish (cachefile
, "unable to fopen for reading");
2348 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2352 *file
= ce
->ce_file
;
2353 *fd
= fileno (ce
->ce_fp
);
2364 return init_encoding (ct
, openFile
);
2369 openFile (CT ct
, char **file
)
2372 char cachefile
[BUFSIZ
];
2373 struct exbody
*e
= ct
->c_ctexbody
;
2374 CE ce
= &ct
->c_cefile
;
2376 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2388 content_error (NULL
, ct
, "missing name parameter");
2392 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2395 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2396 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2400 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2401 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2402 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2406 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2407 if ((fp
= fopen (cachefile
, "w"))) {
2409 char buffer
[BUFSIZ
];
2410 FILE *gp
= ce
->ce_fp
;
2412 fseek (gp
, 0L, SEEK_SET
);
2414 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2416 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2417 advise ("openFile", "fwrite");
2422 admonish (ce
->ce_file
, "error reading");
2423 (void) m_unlink (cachefile
);
2424 } else if (ferror (fp
)) {
2425 admonish (cachefile
, "error writing");
2426 (void) m_unlink (cachefile
);
2433 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2434 *file
= ce
->ce_file
;
2435 return fileno (ce
->ce_fp
);
2445 return init_encoding (ct
, openFTP
);
2450 openFTP (CT ct
, char **file
)
2456 char *bp
, *ftp
, *user
, *pass
;
2457 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2459 CE ce
= &ct
->c_cefile
;
2460 static char *username
= NULL
;
2461 static char *password
= NULL
;
2465 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2471 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2482 if (!e
->eb_name
|| !e
->eb_site
) {
2483 content_error (NULL
, ct
, "missing %s parameter",
2484 e
->eb_name
? "site": "name");
2488 /* Get the buffer ready to go */
2490 buflen
= sizeof(buffer
);
2493 * Construct the query message for user
2495 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2501 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2507 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2508 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2513 if (e
->eb_size
> 0) {
2514 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2519 snprintf (bp
, buflen
, "? ");
2522 * Now, check the answer
2524 if (!read_yes_or_no_if_tty (buffer
))
2529 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2533 ruserpass (e
->eb_site
, &username
, &password
, 0);
2538 ce
->ce_unlink
= (*file
== NULL
);
2540 cachefile
[0] = '\0';
2541 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2542 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2543 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2544 if (*file
== NULL
) {
2551 ce
->ce_file
= mh_xstrdup(*file
);
2553 ce
->ce_file
= mh_xstrdup(cachefile
);
2556 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2557 die("unable to create temporary file in %s",
2560 ce
->ce_file
= mh_xstrdup(tempfile
);
2563 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2564 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2573 vec
[vecp
++] = r1bindex (ftp
, '/');
2574 vec
[vecp
++] = e
->eb_site
;
2577 vec
[vecp
++] = e
->eb_dir
;
2578 vec
[vecp
++] = e
->eb_name
;
2579 vec
[vecp
++] = ce
->ce_file
,
2580 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2581 ? "ascii" : "binary";
2589 adios ("fork", "unable to");
2593 close (fileno (ce
->ce_fp
));
2595 fprintf (stderr
, "unable to exec ");
2601 if (pidXwait (child_id
, NULL
)) {
2602 username
= password
= NULL
;
2612 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2617 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2618 if ((fp
= fopen (cachefile
, "w"))) {
2620 FILE *gp
= ce
->ce_fp
;
2622 fseek (gp
, 0L, SEEK_SET
);
2624 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2626 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2627 advise ("openFTP", "fwrite");
2632 admonish (ce
->ce_file
, "error reading");
2633 (void) m_unlink (cachefile
);
2634 } else if (ferror (fp
)) {
2635 admonish (cachefile
, "error writing");
2636 (void) m_unlink (cachefile
);
2644 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2645 *file
= ce
->ce_file
;
2646 return fileno (ce
->ce_fp
);
2657 return init_encoding (ct
, openMail
);
2662 openMail (CT ct
, char **file
)
2664 int child_id
, fd
, vecp
;
2666 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2667 struct exbody
*e
= ct
->c_ctexbody
;
2668 CE ce
= &ct
->c_cefile
;
2670 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2681 if (!e
->eb_server
) {
2682 content_error (NULL
, ct
, "missing server parameter");
2686 /* Get buffer ready to go */
2688 buflen
= sizeof(buffer
);
2690 /* Now, construct query message */
2691 snprintf (bp
, buflen
, "Retrieve content");
2697 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2703 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2705 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2707 /* Now, check answer */
2708 if (!read_yes_or_no_if_tty (buffer
))
2712 vec
[vecp
++] = r1bindex (mailproc
, '/');
2713 vec
[vecp
++] = e
->eb_server
;
2714 vec
[vecp
++] = "-subject";
2715 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2716 vec
[vecp
++] = "-body";
2717 vec
[vecp
++] = e
->eb_body
;
2723 advise ("fork", "unable to");
2727 execvp (mailproc
, vec
);
2728 fprintf (stderr
, "unable to exec ");
2734 if (pidXwait (child_id
, NULL
) == OK
)
2735 inform("request sent");
2739 if (*file
== NULL
) {
2741 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2742 die("unable to create temporary file in %s",
2745 ce
->ce_file
= mh_xstrdup(tempfile
);
2748 ce
->ce_file
= mh_xstrdup(*file
);
2752 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2753 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2757 /* showproc is for mhshow and mhstore, though mhlist -debug
2758 * prints it, too. */
2759 free(ct
->c_showproc
);
2760 ct
->c_showproc
= mh_xstrdup("true");
2762 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2763 *file
= ce
->ce_file
;
2764 return fileno (ce
->ce_fp
);
2775 return init_encoding (ct
, openURL
);
2780 openURL (CT ct
, char **file
)
2782 struct exbody
*e
= ct
->c_ctexbody
;
2783 CE ce
= &ct
->c_cefile
;
2784 char *urlprog
, *program
;
2785 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2789 struct msgs_array args
= { 0, 0, NULL
};
2792 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2796 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2800 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2812 content_error(NULL
, ct
, "missing url parameter");
2816 ce
->ce_unlink
= (*file
== NULL
);
2818 cachefile
[0] = '\0';
2820 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2821 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2822 if (*file
== NULL
) {
2829 ce
->ce_file
= mh_xstrdup(*file
);
2831 ce
->ce_file
= mh_xstrdup(cachefile
);
2834 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2835 die("unable to create temporary file in %s",
2838 ce
->ce_file
= mh_xstrdup(tempfile
);
2841 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2842 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2846 switch (child_id
= fork()) {
2848 adios ("fork", "unable to");
2852 argsplit_msgarg(&args
, urlprog
, &program
);
2853 app_msgarg(&args
, e
->eb_url
);
2854 app_msgarg(&args
, NULL
);
2855 dup2(fileno(ce
->ce_fp
), 1);
2856 close(fileno(ce
->ce_fp
));
2857 execvp(program
, args
.msgs
);
2858 fprintf(stderr
, "Unable to exec ");
2864 if (pidXwait(child_id
, NULL
)) {
2872 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2877 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2878 if ((fp
= fopen(cachefile
, "w"))) {
2880 FILE *gp
= ce
->ce_fp
;
2882 fseeko(gp
, 0, SEEK_SET
);
2884 while ((cc
= fread(buffer
, sizeof(*buffer
),
2885 sizeof(buffer
), gp
)) > 0)
2886 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2887 advise ("openURL", "fwrite");
2893 admonish(ce
->ce_file
, "error reading");
2894 (void) m_unlink (cachefile
);
2901 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2902 *file
= ce
->ce_file
;
2903 return fileno(ce
->ce_fp
);
2908 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2909 * has to be base64 decoded.
2912 readDigest (CT ct
, char *cp
)
2914 unsigned char *digest
;
2917 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2918 const size_t maxlen
= sizeof ct
->c_digest
;
2920 if (strlen ((char *) digest
) <= maxlen
) {
2921 memcpy (ct
->c_digest
, digest
, maxlen
);
2926 fprintf (stderr
, "MD5 digest=");
2927 for (i
= 0; i
< maxlen
; ++i
) {
2928 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2930 fprintf (stderr
, "\n");
2936 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2937 (int) strlen ((char *) digest
));
2947 /* Multipart parts might have content before the first subpart and/or
2948 after the last subpart that hasn't been stored anywhere else, so do
2951 get_leftover_mp_content (CT ct
, int before
/* or after */)
2953 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2955 bool found_boundary
= false;
2961 char *content
= NULL
;
2963 if (! m
) return NOTOK
;
2966 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2968 /* Isolate the beginning of this part to the beginning of the
2969 first subpart and save any content between them. */
2970 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2971 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2972 boundary
= concat ("--", m
->mp_start
, NULL
);
2974 struct part
*last_subpart
= NULL
;
2975 struct part
*subpart
;
2977 /* Go to the last subpart to get its end position. */
2978 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2979 last_subpart
= subpart
;
2982 if (last_subpart
== NULL
) return NOTOK
;
2984 /* Isolate the end of the last subpart to the end of this part
2985 and save any content between them. */
2986 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2987 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2988 boundary
= concat ("--", m
->mp_stop
, NULL
);
2991 /* Back up by 1 to pick up the newline. */
2992 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2994 /* Don't look beyond beginning of first subpart (before) or
2995 next part (after). */
2996 if (read
> max
) bufp
[read
-max
] = '\0';
2999 if (! strcmp (bufp
, boundary
)) {
3000 found_boundary
= true;
3003 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3004 found_boundary
= true;
3009 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3011 char *old_content
= content
;
3012 content
= concat (content
, bufp
, NULL
);
3016 ? concat ("\n", bufp
, NULL
)
3017 : concat (bufp
, NULL
);
3022 if (found_boundary
|| read
> max
) break;
3024 if (read
> max
) break;
3028 /* Skip the newline if that's all there is. */
3032 /* Remove trailing newline, except at EOF. */
3033 if ((before
|| ! feof (ct
->c_fp
)) &&
3034 (cp
= content
+ strlen (content
)) > content
&&
3039 if (strlen (content
) > 1) {
3041 m
->mp_content_before
= content
;
3043 m
->mp_content_after
= content
;
3058 ct_type_str (int type
)
3061 case CT_APPLICATION
:
3062 return "application";
3078 return "unknown_type";
3084 ct_subtype_str (int type
, int subtype
)
3087 case CT_APPLICATION
:
3089 case APPLICATION_OCTETS
:
3091 case APPLICATION_POSTSCRIPT
:
3092 return "postscript";
3094 return "unknown_app_subtype";
3098 case MESSAGE_RFC822
:
3100 case MESSAGE_PARTIAL
:
3102 case MESSAGE_EXTERNAL
:
3105 return "unknown_msg_subtype";
3111 case MULTI_ALTERNATE
:
3112 return "alternative";
3115 case MULTI_PARALLEL
:
3120 return "unknown_multipart_subtype";
3131 return "unknown_text_subtype";
3134 return "unknown_type";
3140 ct_str_type (const char *type
)
3142 struct str2init
*s2i
;
3144 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3145 if (! strcasecmp (type
, s2i
->si_key
)) {
3149 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3158 ct_str_subtype (int type
, const char *subtype
)
3163 case CT_APPLICATION
:
3164 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3165 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3169 return kv
->kv_value
;
3171 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3172 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3176 return kv
->kv_value
;
3178 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3179 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3183 return kv
->kv_value
;
3185 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3186 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3190 return kv
->kv_value
;
3197 /* Find the content type and InitFunc for the CT. */
3198 const struct str2init
*
3199 get_ct_init (int type
)
3201 const struct str2init
*sp
;
3203 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3204 if (type
== sp
->si_val
) {
3213 ce_str (int encoding
)
3219 return "quoted-printable";
3235 /* Find the content type and InitFunc for the content encoding method. */
3236 const struct str2init
*
3237 get_ce_method (const char *method
)
3239 struct str2init
*sp
;
3241 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3242 if (! strcasecmp (method
, sp
->si_key
)) {
3251 * Parse a series of MIME attributes (or parameters) given a header as
3254 * Arguments include:
3256 * filename - Name of input file (for error messages)
3257 * fieldname - Name of field being processed
3258 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3259 * Updated to point to end of attributes when finished.
3260 * param_head - Pointer to head of parameter list
3261 * param_tail - Pointer to tail of parameter list
3262 * commentp - Pointer to header comment pointer (may be NULL)
3264 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3265 * DONE to indicate a benign error (minor parsing error, but the program
3270 parse_header_attrs (const char *filename
, const char *fieldname
,
3271 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3274 char *cp
= *header_attrp
;
3280 struct sectlist
*next
;
3286 struct sectlist
*sechead
;
3287 struct parmlist
*next
;
3288 } *pp
, *pp2
, *phead
= NULL
;
3290 while (*cp
== ';') {
3291 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3292 bool encoded
= false;
3293 bool partial
= false;
3294 int len
= 0, index
= 0;
3297 while (isspace ((unsigned char) *cp
))
3301 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3306 if (! suppress_extraneous_trailing_semicolon_warning
) {
3307 inform("extraneous trailing ';' in message %s's %s: "
3308 "parameter list", filename
, fieldname
);
3313 /* down case the attribute name */
3314 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3315 *dp
= tolower ((unsigned char) *dp
);
3317 for (up
= dp
; isspace ((unsigned char) *dp
);)
3319 if (dp
== cp
|| *dp
!= '=') {
3320 inform("invalid parameter in message %s's %s: field\n"
3321 " parameter %s (error detected at offset %ld)",
3322 filename
, fieldname
, cp
, (long)(dp
- cp
));
3327 * To handle RFC 2231, we have to deal with the following extensions:
3329 * name*=encoded-value
3330 * name*<N>=part-N-of-a-parameter-value
3331 * name*<N>*=encoded-part-N-of-a-parameter-value
3334 * If there's a * right before the equal sign, it's encoded.
3335 * If there's a * and one or more digits, then it's section N.
3337 * Remember we can have one or the other, or both. cp points to
3338 * beginning of name, up points past the last character in the
3342 for (vp
= cp
; vp
< up
; vp
++) {
3343 if (*vp
== '*' && vp
< up
- 1) {
3347 if (*vp
== '*' && vp
== up
- 1) {
3349 } else if (partial
) {
3350 if (isdigit((unsigned char) *vp
))
3351 index
= *vp
- '0' + index
* 10;
3353 inform("invalid parameter index in message %s's %s: field"
3354 "\n (parameter %s)", filename
, fieldname
, cp
);
3363 * Break out the parameter name and value sections and allocate
3367 nameptr
= mh_xmalloc(len
+ 1);
3368 strncpy(nameptr
, cp
, len
);
3369 nameptr
[len
] = '\0';
3371 for (dp
++; isspace ((unsigned char) *dp
);)
3376 * Single quotes delimit the character set and language tag.
3377 * They are required on the first section (or a complete
3382 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3388 charset
= mh_xmalloc(len
+ 1);
3389 strncpy(charset
, dp
, len
);
3390 charset
[len
] = '\0';
3396 inform("missing charset in message %s's %s: field\n"
3397 " (parameter %s)", filename
, fieldname
, nameptr
);
3403 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3410 lang
= mh_xmalloc(len
+ 1);
3411 strncpy(lang
, dp
, len
);
3418 inform("missing language tag in message %s's %s: field\n"
3419 " (parameter %s)", filename
, fieldname
, nameptr
);
3429 * At this point vp should be pointing at the beginning
3430 * of the encoded value/section. Continue until we reach
3431 * the end or get whitespace. But first, calculate the
3432 * length so we can allocate the correct buffer size.
3435 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3437 if (*(vp
+ 1) == '\0' ||
3438 !isxdigit((unsigned char) *(vp
+ 1)) ||
3439 *(vp
+ 2) == '\0' ||
3440 !isxdigit((unsigned char) *(vp
+ 2))) {
3441 inform("invalid encoded sequence in message %s's %s: field\n"
3442 " (parameter %s)", filename
, fieldname
, nameptr
);
3453 up
= valptr
= mh_xmalloc(len
+ 1);
3455 for (vp
= dp
; istoken(*vp
); vp
++) {
3457 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3468 * A "normal" string. If it's got a leading quote, then we
3469 * strip the quotes out. Otherwise go until we reach the end
3470 * or get whitespace. Note we scan it twice; once to get the
3471 * length, then the second time copies it into the destination
3478 for (cp
= dp
+ 1;;) {
3482 inform("invalid quoted-string in message %s's %s: field\n"
3483 " (parameter %s)", filename
, fieldname
, nameptr
);
3503 for (cp
= dp
; istoken (*cp
); cp
++) {
3508 valptr
= mh_xmalloc(len
+ 1);
3512 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3520 strncpy(valptr
, cp
= dp
, len
);
3528 * If 'partial' is set, we don't allocate a parameter now. We
3529 * put it on the parameter linked list to be reassembled later.
3531 * "phead" points to a list of all parameters we need to reassemble.
3532 * Each parameter has a list of sections. We insert the sections in
3537 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3538 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3553 * Insert this into the section linked list
3561 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3562 sp
->next
= pp
->sechead
;
3565 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3566 if (sp2
->index
== sp
->index
) {
3567 inform("duplicate index (%d) in message %s's %s: field"
3568 "\n (parameter %s)", sp
->index
, filename
,
3569 fieldname
, nameptr
);
3572 if (sp2
->index
< sp
->index
&&
3573 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3574 sp
->next
= sp2
->next
;
3581 inform("Internal error: cannot insert partial param "
3582 "in message %s's %s: field\n (parameter %s)",
3583 filename
, fieldname
, nameptr
);
3589 * Save our charset and lang tags.
3592 if (index
== 0 && encoded
) {
3594 pp
->charset
= charset
;
3599 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3600 pm
->pm_charset
= charset
;
3604 while (isspace ((unsigned char) *cp
))
3608 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3614 * Now that we're done, reassemble all of the partial parameters.
3617 for (pp
= phead
; pp
!= NULL
; ) {
3621 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3622 if (sp
->index
!= pindex
++) {
3623 inform("missing section %d for parameter in message "
3624 "%s's %s: field\n (parameter %s)", pindex
- 1,
3625 filename
, fieldname
, pp
->name
);
3631 p
= q
= mh_xmalloc(tlen
+ 1);
3632 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3633 memcpy(q
, sp
->value
, sp
->len
);
3643 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3644 pm
->pm_charset
= pp
->charset
;
3645 pm
->pm_lang
= pp
->lang
;
3656 * Return the charset for a particular content type.
3660 content_charset (CT ct
)
3662 char *ret_charset
= NULL
;
3664 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3666 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3671 * Create a string based on a list of output parameters. Assume that this
3672 * parameter string will be appended to an existing header, so start out
3673 * with the separator (;). Perform RFC 2231 encoding when necessary.
3677 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3679 char *paramout
= NULL
;
3680 char line
[CPERLIN
* 2], *q
;
3681 int curlen
, index
, cont
, encode
, i
;
3682 size_t valoff
, numchars
;
3684 while (params
!= NULL
) {
3690 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3693 if (strlen(params
->pm_name
) > CPERLIN
) {
3694 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3699 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3702 * Loop until we get a parameter that fits within a line. We
3703 * assume new lines start with a tab, so check our overflow based
3713 * At this point we're definitely continuing the line, so
3714 * be sure to include the parameter name and section index.
3717 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3718 params
->pm_name
, index
);
3721 * Both of these functions do a NUL termination
3725 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3726 numchars
, valoff
, index
);
3728 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3738 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3743 * "line" starts with a ;\n\t, so that doesn't count against
3744 * the length. But add 8 since it starts with a tab; that's
3745 * how we end up with 5.
3748 initialwidth
= strlen(line
) + 5;
3751 * At this point the line should be built, so add it to our
3752 * current output buffer.
3755 paramout
= add(line
, paramout
);
3759 * If this won't fit on the line, start a new one. Save room in
3760 * case we need a semicolon on the end
3763 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3775 * At this point, we're either finishing a continued parameter, or
3776 * we're working on a new one.
3780 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3781 params
->pm_name
, index
);
3783 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3788 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3789 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3791 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3792 strlen(params
->pm_value
+ valoff
), valoff
);
3799 paramout
= add(line
, paramout
);
3800 initialwidth
+= strlen(line
);
3802 params
= params
->pm_next
;
3806 *offsetout
= initialwidth
;
3812 * Calculate the size of a parameter.
3816 * pm - The parameter being output
3817 * index - If continuing the parameter, the index of the section
3819 * valueoff - The current offset into the parameter value that we're
3820 * working on (previous sections have consumed valueoff bytes).
3821 * encode - Set if we should perform encoding on this parameter section
3822 * (given that we're consuming bytesfit bytes).
3823 * cont - Set if the remaining data in value will not fit on a single
3824 * line and will need to be continued.
3825 * bytesfit - The number of bytes that we can consume from the parameter
3826 * value and still fit on a completely new line. The
3827 * calculation assumes the new line starts with a tab,
3828 * includes the parameter name and any encoding, and fits
3829 * within CPERLIN bytes. Will always be at least 1.
3833 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3836 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3837 size_t len
= 0, fit
= 0;
3838 int fitlimit
= 0, eightbit
, maxfit
;
3843 * Add up the length. First, start with the parameter name.
3846 len
= strlen(pm
->pm_name
);
3849 * Scan the parameter value and see if we need to do encoding for this
3853 eightbit
= contains8bit(start
, NULL
);
3856 * Determine if we need to encode this section. Encoding is necessary if:
3858 * - There are any 8-bit characters at all and we're on the first
3860 * - There are 8-bit characters within N bytes of our section start.
3861 * N is calculated based on the number of bytes it would take to
3862 * reach CPERLIN. Specifically:
3863 * 8 (starting tab) +
3864 * strlen(param name) +
3865 * 4 ('* for section marker, '=', opening/closing '"')
3867 * is the number of bytes used by everything that isn't part of the
3868 * value. So that gets subtracted from CPERLIN.
3871 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3872 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3873 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3877 len
++; /* Add in equal sign */
3881 * We're using maxfit as a marker for how many characters we can
3882 * fit into the line. Bump it by two because we're not using quotes
3889 * If we don't have a charset or language tag in this parameter,
3893 if (! pm
->pm_charset
) {
3894 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3895 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3896 die("8-bit characters in parameter \"%s\", but "
3897 "local character set is US-ASCII", pm
->pm_name
);
3900 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3902 len
++; /* For the encoding marker */
3905 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3910 * We know we definitely need to include an index. maxfit already
3911 * includes the section marker.
3913 len
+= strlen(indexchar
);
3915 for (p
= start
; *p
!= '\0'; p
++) {
3916 if (isparamencode(*p
)) {
3924 * Just so there's no confusion: maxfit is counting OUTPUT
3925 * characters (post-encoding). fit is counting INPUT characters.
3927 if (! fitlimit
&& maxfit
>= 0)
3929 else if (! fitlimit
)
3934 * Calculate the string length, but add room for quoting \
3935 * and " if necessary. Also account for quotes at beginning
3938 for (p
= start
; *p
!= '\0'; p
++) {
3949 if (! fitlimit
&& maxfit
>= 0)
3951 else if (! fitlimit
)
3968 * Output an encoded parameter string.
3972 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3973 size_t valueoff
, int index
)
3975 size_t outlen
= 0, n
;
3976 char *endptr
= output
+ len
, *p
;
3979 * First, output the marker for an encoded string.
3987 * If the index is 0, output the character set and language tag.
3988 * If theses were NULL, they should have already been filled in
3993 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3997 if (output
> endptr
) {
3998 inform("Internal error: parameter buffer overflow");
4004 * Copy over the value, encoding if necessary
4007 p
= pm
->pm_value
+ valueoff
;
4008 while (valuelen
-- > 0) {
4009 if (isparamencode(*p
)) {
4010 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4017 if (output
> endptr
) {
4018 inform("Internal error: parameter buffer overflow");
4029 * Output a "normal" parameter, without encoding. Be sure to escape
4030 * quotes and backslashes if necessary.
4034 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4038 char *endptr
= output
+ len
, *p
;
4044 p
= pm
->pm_value
+ valueoff
;
4046 while (valuelen
-- > 0) {
4057 if (output
> endptr
) {
4058 inform("Internal error: parameter buffer overflow");
4063 if (output
- 2 > endptr
) {
4064 inform("Internal error: parameter buffer overflow");
4075 * Add a parameter to the parameter linked list
4079 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4084 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4085 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4088 (*last
)->pm_next
= pm
;
4099 * Either replace a current parameter with a new value, or add the parameter
4100 * to the parameter linked list.
4104 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4108 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4109 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4111 * If nocopy is set, it's assumed that we own both name
4112 * and value. We don't need name, so we discard it now.
4117 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4122 return add_param(first
, last
, name
, value
, nocopy
);
4126 * Retrieve a parameter value from a parameter linked list. If the parameter
4127 * value needs converted to the local character set, do that now.
4131 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4133 while (first
!= NULL
) {
4134 if (strcasecmp(name
, first
->pm_name
) == 0) {
4136 return first
->pm_value
;
4137 return getcpy(get_param_value(first
, replace
));
4139 first
= first
->pm_next
;
4146 * Return a parameter value, converting to the local character set if
4151 get_param_value(PM pm
, char replace
)
4153 static char buffer
[4096]; /* I hope no parameters are larger */
4154 size_t bufsize
= sizeof(buffer
);
4159 ICONV_CONST
char *p
;
4160 #else /* HAVE_ICONV */
4162 #endif /* HAVE_ICONV */
4167 * If we don't have a character set indicated, it's assumed to be
4168 * US-ASCII. If it matches our character set, we don't need to convert
4172 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4173 strlen(pm
->pm_charset
))) {
4174 return pm
->pm_value
;
4178 * In this case, we need to convert. If we have iconv support, use
4179 * that. Otherwise, go through and simply replace every non-ASCII
4180 * character with the substitution character.
4185 bufsize
= sizeof(buffer
);
4186 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4188 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4189 if (cd
== (iconv_t
) -1) {
4193 inbytes
= strlen(pm
->pm_value
);
4197 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4198 if (errno
!= EILSEQ
) {
4203 * Reset shift state, substitute our character,
4204 * try to restart conversion.
4207 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4220 for (++p
, --inbytes
;
4221 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4240 #endif /* HAVE_ICONV */
4243 * Take everything non-ASCII and substitute the replacement character
4247 bufsize
= sizeof(buffer
);
4248 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4249 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))