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/m_gmprot.h"
10 #include "sbr/m_getfld.h"
11 #include "sbr/read_yes_or_no_if_tty.h"
12 #include "sbr/concat.h"
13 #include "sbr/r1bindex.h"
14 #include "sbr/ruserpass.h"
15 #include "sbr/fmt_rfc2047.h"
17 #include "sbr/check_charset.h"
18 #include "sbr/getcpy.h"
19 #include "sbr/context_find.h"
20 #include "sbr/pidstatus.h"
21 #include "sbr/arglist.h"
22 #include "sbr/error.h"
28 #include "h/mhparse.h"
31 #include "h/mhcachesbr.h"
32 #include "sbr/m_mktemp.h"
36 #endif /* HAVE_ICONV */
37 #include "sbr/base64.h"
42 int checksw
= 0; /* check Content-MD5 field */
45 * These are for mhfixmsg to:
46 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
48 * 2) Suppress the warning about bogus multipart content, and report it.
49 * 3) Suppress the warning about extraneous trailing ';' in header parameter
52 bool skip_mp_cte_check
;
53 bool suppress_bogus_mp_content_warning
;
54 bool bogus_mp_content
;
55 bool suppress_extraneous_trailing_semicolon_warning
;
58 * By default, suppress warning about multiple MIME-Version header fields.
60 bool suppress_multiple_mime_version_warning
= true;
62 /* list of preferred type/subtype pairs, for -prefer */
63 mime_type_subtype mime_preference
[NPREFS
];
68 * Structures for TEXT messages
70 struct k2v SubText
[] = {
71 { "plain", TEXT_PLAIN
},
72 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
73 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
74 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
77 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
80 * Structures for MULTIPART messages
82 struct k2v SubMultiPart
[] = {
83 { "mixed", MULTI_MIXED
},
84 { "alternative", MULTI_ALTERNATE
},
85 { "digest", MULTI_DIGEST
},
86 { "parallel", MULTI_PARALLEL
},
87 { "related", MULTI_RELATED
},
88 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
92 * Structures for MESSAGE messages
94 struct k2v SubMessage
[] = {
95 { "rfc822", MESSAGE_RFC822
},
96 { "partial", MESSAGE_PARTIAL
},
97 { "external-body", MESSAGE_EXTERNAL
},
98 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
102 * Structure for APPLICATION messages
104 struct k2v SubApplication
[] = {
105 { "octet-stream", APPLICATION_OCTETS
},
106 { "postscript", APPLICATION_POSTSCRIPT
},
107 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
111 * Mapping of names of CTE types in mhbuild directives
113 static struct k2v EncodingType
[] = {
116 { "q-p", CE_QUOTED
},
117 { "quoted-printable", CE_QUOTED
},
118 { "b64", CE_BASE64
},
119 { "base64", CE_BASE64
},
127 static CT
get_content (FILE *, char *, int);
128 static int get_comment (const char *, const char *, char **, char **);
130 static int InitGeneric (CT
);
131 static int InitText (CT
);
132 static int InitMultiPart (CT
);
133 static void reverse_parts (CT
);
134 static void prefer_parts(CT ct
);
135 static int InitMessage (CT
);
136 static int InitApplication (CT
);
137 static int init_encoding (CT
, OpenCEFunc
);
138 static unsigned long size_encoding (CT
);
139 static int InitBase64 (CT
);
140 static int openBase64 (CT
, char **);
141 static int InitQuoted (CT
);
142 static int openQuoted (CT
, char **);
143 static int Init7Bit (CT
);
144 static int openExternal (CT
, CT
, CE
, char **, int *);
145 static int InitFile (CT
);
146 static int openFile (CT
, char **);
147 static int InitFTP (CT
);
148 static int openFTP (CT
, char **);
149 static int InitMail (CT
);
150 static int openMail (CT
, char **);
151 static int readDigest (CT
, char *);
152 static int get_leftover_mp_content (CT
, int);
153 static int InitURL (CT
);
154 static int openURL (CT
, char **);
155 static int parse_header_attrs (const char *, const char *, char **, PM
*,
157 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
158 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
159 static int get_dispo (char *, CT
, int);
161 struct str2init str2cts
[] = {
162 { "application", CT_APPLICATION
, InitApplication
},
163 { "audio", CT_AUDIO
, InitGeneric
},
164 { "image", CT_IMAGE
, InitGeneric
},
165 { "message", CT_MESSAGE
, InitMessage
},
166 { "multipart", CT_MULTIPART
, InitMultiPart
},
167 { "text", CT_TEXT
, InitText
},
168 { "video", CT_VIDEO
, InitGeneric
},
169 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
170 { NULL
, CT_UNKNOWN
, NULL
},
173 struct str2init str2ces
[] = {
174 { "base64", CE_BASE64
, InitBase64
},
175 { "quoted-printable", CE_QUOTED
, InitQuoted
},
176 { "8bit", CE_8BIT
, Init7Bit
},
177 { "7bit", CE_7BIT
, Init7Bit
},
178 { "binary", CE_BINARY
, Init7Bit
},
179 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
180 { NULL
, CE_UNKNOWN
, NULL
},
184 * NOTE WELL: si_key MUST NOT have value of NOTOK
186 * si_val is 1 if access method is anonymous.
188 struct str2init str2methods
[] = {
189 { "afs", 1, InitFile
},
190 { "anon-ftp", 1, InitFTP
},
191 { "ftp", 0, InitFTP
},
192 { "local-file", 0, InitFile
},
193 { "mail-server", 0, InitMail
},
194 { "url", 0, InitURL
},
200 * Main entry point for parsing a MIME message or file.
201 * It returns the Content structure for the top level
202 * entity in the file.
206 parse_mime (char *file
)
215 bogus_mp_content
= false;
218 * Check if file is actually standard input
220 if ((is_stdin
= !(strcmp (file
, "-")))) {
221 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
223 advise("mhparse", "unable to create temporary file in %s",
227 file
= mh_xstrdup(tfile
);
229 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
230 if (fwrite(buffer
, 1, n
, fp
) != n
) {
231 (void) m_unlink (file
);
232 advise (file
, "error copying to temporary file");
238 if (ferror (stdin
)) {
239 (void) m_unlink (file
);
240 advise ("stdin", "error reading");
244 (void) m_unlink (file
);
245 advise (file
, "error writing");
248 fseek (fp
, 0L, SEEK_SET
);
249 } else if (stat (file
, &statbuf
) == NOTOK
) {
250 advise (file
, "unable to stat");
252 } else if (S_ISDIR(statbuf
.st_mode
)) {
253 /* Don't try to parse a directory. */
254 inform("%s is a directory", file
);
256 } else if ((fp
= fopen (file
, "r")) == NULL
) {
257 advise (file
, "unable to read");
261 if (!(ct
= get_content (fp
, file
, 1))) {
263 (void) m_unlink (file
);
264 inform("unable to decode %s", file
);
269 ct
->c_unlink
= 1; /* temp file to remove */
273 if (ct
->c_end
== 0L) {
274 fseek (fp
, 0L, SEEK_END
);
275 ct
->c_end
= ftell (fp
);
278 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
290 * Main routine for reading/parsing the headers
291 * of a message content.
293 * toplevel = 1 # we are at the top level of the message
294 * toplevel = 0 # we are inside message type or multipart type
295 * # other than multipart/digest
296 * toplevel = -1 # we are inside multipart/digest
297 * NB: on failure we will fclose(in)!
301 get_content (FILE *in
, char *file
, int toplevel
)
304 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
308 m_getfld_state_t gstate
;
310 /* allocate the content structure */
313 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
314 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
317 * Parse the header fields for this
318 * content into a linked list.
320 gstate
= m_getfld_state_init(in
);
321 m_getfld_track_filepos2(&gstate
);
322 for (compnum
= 1;;) {
323 int bufsz
= sizeof buf
;
324 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
329 /* get copies of the buffers */
330 np
= mh_xstrdup(name
);
331 vp
= mh_xstrdup(buf
);
333 /* if necessary, get rest of field */
334 while (state
== FLDPLUS
) {
336 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
337 vp
= add (buf
, vp
); /* add to previous value */
340 /* Now add the header data to the list */
341 add_header (ct
, np
, vp
);
343 /* continue, to see if this isn't the last header field */
344 ct
->c_begin
= ftell (in
) + 1;
348 /* There are two cases. The unusual one is when there is no
349 * blank line between the headers and the body. This is
350 * indicated by the name of the header starting with `:'.
352 * For both cases, normal first, `1' is the desired c_begin
353 * file position for the start of the body, and `2' is the
354 * file position when buf is returned.
356 * f o o : b a r \n \n b o d y \n bufsz = 6
358 * f o o : b a r \n b o d y \n bufsz = 4
361 * For the normal case, bufsz includes the
362 * header-terminating `\n', even though it is not in buf,
363 * but bufsz isn't affected when it's missing in the unusual
365 if (name
[0] == ':') {
366 ct
->c_begin
= ftell(in
) - bufsz
;
368 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
373 ct
->c_begin
= ftell (in
);
378 die("message format error in component #%d", compnum
);
381 die("getfld() returned %d", state
);
384 /* break out of the loop */
387 m_getfld_state_destroy (&gstate
);
390 * Read the content headers. We will parse the
391 * MIME related header fields into their various
392 * structures and set internal flags related to
393 * content type/subtype, etc.
396 hp
= ct
->c_first_hf
; /* start at first header field */
398 /* Get MIME-Version field */
399 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
404 vrsn
= mh_xstrdup(FENDNULL(hp
->value
));
406 /* Now, cleanup this field */
409 while (isspace ((unsigned char) *cp
))
411 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
413 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
414 if (!isspace ((unsigned char) *dp
))
418 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
421 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
424 for (dp
= cp
; istoken (*dp
); dp
++)
428 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
431 inform("message %s has unknown value for %s: field (%s), continuing...",
432 ct
->c_file
, VRSN_FIELD
, cp
);
437 if (! suppress_multiple_mime_version_warning
)
438 inform("message %s has multiple %s: fields",
439 ct
->c_file
, VRSN_FIELD
);
443 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
444 /* Get Content-Type field */
445 struct str2init
*s2i
;
446 CI ci
= &ct
->c_ctinfo
;
448 /* Check if we've already seen a Content-Type header */
450 inform("message %s has multiple %s: fields",
451 ct
->c_file
, TYPE_FIELD
);
455 /* Parse the Content-Type field */
456 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
460 * Set the Init function and the internal
461 * flag for this content type.
463 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
464 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
466 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
468 ct
->c_type
= s2i
->si_val
;
469 ct
->c_ctinitfnx
= s2i
->si_init
;
471 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
472 /* Get Content-Transfer-Encoding field */
474 struct str2init
*s2i
;
477 * Check if we've already seen the
478 * Content-Transfer-Encoding field
481 inform("message %s has multiple %s: fields",
482 ct
->c_file
, ENCODING_FIELD
);
486 /* get copy of this field */
487 ct
->c_celine
= cp
= mh_xstrdup(FENDNULL(hp
->value
));
489 while (isspace ((unsigned char) *cp
))
491 for (dp
= cp
; istoken (*dp
); dp
++)
497 * Find the internal flag and Init function
498 * for this transfer encoding.
500 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
501 if (!strcasecmp (cp
, s2i
->si_key
))
503 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
506 ct
->c_encoding
= s2i
->si_val
;
508 /* Call the Init function for this encoding */
509 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
512 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
513 /* Get Content-MD5 field */
519 if (ct
->c_digested
) {
520 inform("message %s has multiple %s: fields",
521 ct
->c_file
, MD5_FIELD
);
525 ep
= cp
= mh_xstrdup(FENDNULL(hp
->value
)); /* get a copy */
527 while (isspace ((unsigned char) *cp
))
529 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
531 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
532 if (!isspace ((unsigned char) *dp
))
536 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
539 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
544 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
552 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
553 /* Get Content-ID field */
554 ct
->c_id
= add (hp
->value
, ct
->c_id
);
556 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
557 /* Get Content-Description field */
558 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
560 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
561 /* Get Content-Disposition field */
562 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
567 hp
= hp
->next
; /* next header field */
571 * Check if we saw a Content-Type field.
572 * If not, then assign a default value for
573 * it, and the Init function.
577 * If we are inside a multipart/digest message,
578 * so default type is message/rfc822
581 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
583 ct
->c_type
= CT_MESSAGE
;
584 ct
->c_ctinitfnx
= InitMessage
;
587 * Else default type is text/plain
589 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
591 ct
->c_type
= CT_TEXT
;
592 ct
->c_ctinitfnx
= InitText
;
596 /* Use default Transfer-Encoding, if necessary */
598 ct
->c_encoding
= CE_7BIT
;
611 * small routine to add header field to list
615 add_header (CT ct
, char *name
, char *value
)
619 /* allocate header field structure */
622 /* link data into header structure */
627 /* link header structure into the list */
628 if (ct
->c_first_hf
== NULL
) {
629 ct
->c_first_hf
= hp
; /* this is the first */
632 ct
->c_last_hf
->next
= hp
; /* add it to the end */
641 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
642 * directives. Fills in the information of the CTinfo structure.
645 get_ctinfo (char *cp
, CT ct
, int magic
)
654 /* store copy of Content-Type line */
655 cp
= ct
->c_ctline
= mh_xstrdup(FENDNULL(cp
));
657 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
660 /* change newlines to spaces */
661 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
664 /* trim trailing spaces */
665 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
666 if (!isspace ((unsigned char) *dp
))
671 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
673 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
674 &ci
->ci_comment
) == NOTOK
)
677 for (dp
= cp
; istoken (*dp
); dp
++)
681 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
686 inform("invalid %s: field in message %s (empty type)",
687 TYPE_FIELD
, ct
->c_file
);
690 to_lower(ci
->ci_type
);
692 while (isspace ((unsigned char) *cp
))
695 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
696 &ci
->ci_comment
) == NOTOK
)
701 ci
->ci_subtype
= mh_xstrdup("");
706 while (isspace ((unsigned char) *cp
))
709 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
710 &ci
->ci_comment
) == NOTOK
)
713 for (dp
= cp
; istoken (*dp
); dp
++)
717 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
721 if (!*ci
->ci_subtype
) {
722 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
723 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
726 to_lower(ci
->ci_subtype
);
729 while (isspace ((unsigned char) *cp
))
732 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
733 &ci
->ci_comment
) == NOTOK
)
736 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
737 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
738 &ci
->ci_comment
)) != OK
) {
739 return status
== NOTOK
? NOTOK
: OK
;
743 * Get any <Content-Id> given in buffer
745 if (magic
&& *cp
== '<') {
748 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
749 inform("invalid ID in message %s", ct
->c_file
);
755 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
761 while (isspace ((unsigned char) *cp
))
766 * Get any [Content-Description] given in buffer.
768 if (magic
&& *cp
== '[') {
770 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
774 inform("invalid description in message %s", ct
->c_file
);
782 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
788 while (isspace ((unsigned char) *cp
))
793 * Get any {Content-Disposition} given in buffer.
795 if (magic
&& *cp
== '{') {
797 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
801 inform("invalid disposition in message %s", ct
->c_file
);
809 if (get_dispo(cp
, ct
, 1) != OK
)
815 while (isspace ((unsigned char) *cp
))
820 * Get any extension directives (right now just the content transfer
821 * encoding, but maybe others) that we care about.
824 if (magic
&& *cp
== '*') {
826 * See if it's a CTE we match on
831 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
835 inform("invalid null transfer encoding specification");
842 ct
->c_reqencoding
= CE_UNKNOWN
;
844 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
845 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
846 ct
->c_reqencoding
= kv
->kv_value
;
851 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
852 inform("invalid CTE specification: \"%s\"", dp
);
856 while (isspace ((unsigned char) *cp
))
861 * Check if anything is left over
865 ci
->ci_magic
= mh_xstrdup(cp
);
867 /* If there is a Content-Disposition header and it doesn't
868 have a *filename=, extract it from the magic contents.
869 The r1bindex call skips any leading directory
871 if (ct
->c_dispo_type
&&
872 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
873 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
874 r1bindex(ci
->ci_magic
, '/'), 0);
878 inform("extraneous information in message %s's %s: field\n"
879 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
887 * Parse out a Content-Disposition header. A lot of this is cribbed from
891 get_dispo (char *cp
, CT ct
, int buildflag
)
893 char *dp
, *dispoheader
;
898 * Save the whole copy of the Content-Disposition header, unless we're
899 * processing a mhbuild directive. A NULL c_dispo will be a flag to
900 * mhbuild that the disposition header needs to be generated at that
904 dispoheader
= cp
= mh_xstrdup(FENDNULL(cp
));
906 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
909 /* change newlines to spaces */
910 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
913 /* trim trailing spaces */
914 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
915 if (!isspace ((unsigned char) *dp
))
920 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
922 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
928 for (dp
= cp
; istoken (*dp
); dp
++)
932 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
936 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
939 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
940 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
942 if (status
== NOTOK
) {
947 inform("extraneous information in message %s's %s: field\n (%s)",
948 ct
->c_file
, DISPO_FIELD
, cp
);
954 ct
->c_dispo
= dispoheader
;
961 get_comment (const char *filename
, const char *fieldname
, char **ap
,
966 char c
, buffer
[BUFSIZ
], *dp
;
976 inform("invalid comment in message %s's %s: field",
977 filename
, fieldname
);
982 if ((c
= *cp
++) == '\0')
1005 if ((dp
= *commentp
)) {
1006 *commentp
= concat (dp
, " ", buffer
, NULL
);
1009 *commentp
= mh_xstrdup(buffer
);
1013 while (isspace ((unsigned char) *cp
))
1024 * Handles content types audio, image, and video.
1025 * There's not much to do right here.
1033 return OK
; /* not much to do here */
1044 char buffer
[BUFSIZ
];
1049 CI ci
= &ct
->c_ctinfo
;
1051 /* check for missing subtype */
1052 if (!*ci
->ci_subtype
)
1053 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1056 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1058 /* allocate text character set structure */
1060 ct
->c_ctparams
= (void *) t
;
1062 /* scan for charset parameter */
1063 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1064 if (!strcasecmp (pm
->pm_name
, "charset"))
1067 /* check if content specified a character set */
1069 chset
= pm
->pm_value
;
1070 t
->tx_charset
= CHARSET_SPECIFIED
;
1072 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1076 * If we can not handle character set natively,
1077 * then check profile for string to modify the
1078 * terminal or display method.
1080 * termproc is for mhshow, though mhlist -debug prints it, too.
1082 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1083 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1084 if ((cp
= context_find (buffer
)))
1085 ct
->c_termproc
= mh_xstrdup(cp
);
1097 InitMultiPart (CT ct
)
1107 struct multipart
*m
;
1108 struct part
*part
, **next
;
1109 CI ci
= &ct
->c_ctinfo
;
1114 * The encoding for multipart messages must be either
1115 * 7bit, 8bit, or binary (per RFC 2045).
1117 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1118 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1119 /* Copy the Content-Transfer-Encoding header field body so we can
1120 remove any trailing whitespace and leading blanks from it. */
1121 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1123 bp
= cte
+ strlen (cte
) - 1;
1124 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1125 for (bp
= cte
; isblank((unsigned char)*bp
); ++bp
) continue;
1127 inform("\"%s/%s\" type in message %s must be encoded in\n"
1128 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1129 "mhfixmsg -fixcte can fix it, or\n"
1130 "manually edit the file and change the \"%s\"\n"
1131 "Content-Transfer-Encoding to one of those. For now, continuing...",
1132 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1139 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1142 * Check for "boundary" parameter, which is
1143 * required for multipart messages.
1146 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1147 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1153 /* complain if boundary parameter is missing */
1155 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1156 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1160 /* allocate primary structure for multipart info */
1162 ct
->c_ctparams
= (void *) m
;
1164 /* check if boundary parameter contains only whitespace characters */
1165 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1168 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1169 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1173 /* remove trailing whitespace from boundary parameter */
1174 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1175 if (!isspace ((unsigned char) *dp
))
1179 /* record boundary separators */
1180 m
->mp_start
= concat (bp
, "\n", NULL
);
1181 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1183 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1184 advise (ct
->c_file
, "unable to open for reading");
1188 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1190 next
= &m
->mp_parts
;
1194 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1199 if (bufp
[0] != '-' || bufp
[1] != '-')
1202 if (strcmp (bufp
+ 2, m
->mp_start
))
1207 next
= &part
->mp_next
;
1209 if (!(p
= get_content (fp
, ct
->c_file
,
1210 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1218 fseek (fp
, pos
, SEEK_SET
);
1221 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1225 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1226 if (p
->c_end
< p
->c_begin
)
1227 p
->c_begin
= p
->c_end
;
1232 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1237 if (! suppress_bogus_mp_content_warning
) {
1238 inform("bogus multipart content in message %s", ct
->c_file
);
1240 bogus_mp_content
= true;
1242 if (!inout
&& part
) {
1244 p
->c_end
= ct
->c_end
;
1246 if (p
->c_begin
>= p
->c_end
) {
1247 for (next
= &m
->mp_parts
; *next
!= part
;
1248 next
= &((*next
)->mp_next
))
1257 /* reverse the order of the parts for multipart/alternative */
1258 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1264 * label all subparts with part number, and
1265 * then initialize the content of the subpart.
1270 char partnam
[BUFSIZ
];
1273 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1274 pp
= partnam
+ strlen (partnam
);
1279 for (part
= m
->mp_parts
, partnum
= 1; part
;
1280 part
= part
->mp_next
, partnum
++) {
1283 sprintf (pp
, "%d", partnum
);
1284 p
->c_partno
= mh_xstrdup(partnam
);
1286 /* initialize the content of the subparts */
1287 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1296 get_leftover_mp_content (ct
, 1);
1297 get_leftover_mp_content (ct
, 0);
1307 * reverse the order of the parts of a multipart/alternative,
1308 * presumably to put the "most favored" alternative first, for
1309 * ease of choosing/displaying it later on. from a mail message on
1310 * nmh-workers, from kenh:
1311 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1312 * see code in mhn that did the same thing... According to the RCS
1313 * logs, that code was around from the initial checkin of mhn.c by
1314 * John Romine in 1992, which is as far back as we have."
1317 reverse_parts (CT ct
)
1319 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1323 /* Reverse the order of its parts by walking the mp_parts list
1324 and pushing each node to the front. */
1325 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1326 next
= part
->mp_next
;
1327 part
->mp_next
= m
->mp_parts
;
1333 move_preferred_part(CT ct
, mime_type_subtype
*pref
)
1335 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1336 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1340 /* move the matching part(s) to the head of the list: walk the
1341 * list of parts, move matching parts to a new list (maintaining
1342 * their order), and finally, concatenate the old list onto the
1349 head
->mp_next
= m
->mp_parts
;
1350 nhead
->mp_next
= NULL
;
1354 part
= head
->mp_next
;
1355 while (part
!= NULL
) {
1356 ci
= &part
->mp_part
->c_ctinfo
;
1357 if (!strcasecmp(ci
->ci_type
, pref
->type
) &&
1359 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1360 prev
->mp_next
= part
->mp_next
;
1361 part
->mp_next
= NULL
;
1362 ntail
->mp_next
= part
;
1364 part
= prev
->mp_next
;
1367 part
= prev
->mp_next
;
1370 ntail
->mp_next
= head
->mp_next
;
1371 m
->mp_parts
= nhead
->mp_next
;
1375 * move parts that match the user's preferences (-prefer) to the head
1376 * of the line. process preferences in reverse so first one given
1377 * ends up first in line
1383 for (i
= 0; i
< npreferred
; i
++)
1384 move_preferred_part(ct
, mime_preference
+ i
);
1389 /* parse_mime() arranges alternates in reverse (priority) order. This
1390 function can be used to reverse them back. This will put, for
1391 example, a text/plain part before a text/html part in a
1392 multipart/alternative part, for example, where it belongs. */
1394 reverse_alternative_parts (CT ct
)
1396 if (ct
->c_type
== CT_MULTIPART
) {
1397 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1400 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1404 /* And call recursively on each part of a multipart. */
1405 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1406 reverse_alternative_parts (part
->mp_part
);
1419 CI ci
= &ct
->c_ctinfo
;
1421 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1422 inform("\"%s/%s\" type in message %s should be encoded in "
1423 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1428 /* check for missing subtype */
1429 if (!*ci
->ci_subtype
)
1430 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1433 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1435 switch (ct
->c_subtype
) {
1436 case MESSAGE_RFC822
:
1439 case MESSAGE_PARTIAL
:
1445 ct
->c_ctparams
= (void *) p
;
1447 /* scan for parameters "id", "number", and "total" */
1448 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1449 if (!strcasecmp (pm
->pm_name
, "id")) {
1450 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1453 if (!strcasecmp (pm
->pm_name
, "number")) {
1454 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1455 || p
->pm_partno
< 1) {
1457 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1458 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1459 ct
->c_file
, TYPE_FIELD
);
1464 if (!strcasecmp (pm
->pm_name
, "total")) {
1465 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1474 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1475 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1476 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1482 case MESSAGE_EXTERNAL
:
1490 ct
->c_ctparams
= (void *) e
;
1493 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1494 advise (ct
->c_file
, "unable to open for reading");
1498 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1500 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1508 p
->c_ceopenfnx
= NULL
;
1509 if ((exresult
= params_external (ct
, 0)) != NOTOK
1510 && p
->c_ceopenfnx
== openMail
) {
1514 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1516 content_error (NULL
, ct
,
1517 "empty body for access-type=mail-server");
1521 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1522 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1524 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1526 adios ("failed", "fread");
1529 die("unexpected EOF from fread");
1532 bp
+= cc
, size
-= cc
;
1539 p
->c_end
= p
->c_begin
;
1544 if (exresult
== NOTOK
)
1546 if (e
->eb_flags
== NOTOK
)
1549 switch (p
->c_type
) {
1554 if (p
->c_subtype
!= MESSAGE_RFC822
)
1558 e
->eb_partno
= ct
->c_partno
;
1560 (*p
->c_ctinitfnx
) (p
);
1575 params_external (CT ct
, int composing
)
1578 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1579 CI ci
= &ct
->c_ctinfo
;
1581 ct
->c_ceopenfnx
= NULL
;
1582 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1583 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1584 struct str2init
*s2i
;
1585 CT p
= e
->eb_content
;
1587 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1588 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1591 e
->eb_access
= pm
->pm_value
;
1592 e
->eb_flags
= NOTOK
;
1593 p
->c_encoding
= CE_EXTERNAL
;
1596 e
->eb_access
= s2i
->si_key
;
1597 e
->eb_flags
= s2i
->si_val
;
1598 p
->c_encoding
= CE_EXTERNAL
;
1600 /* Call the Init function for this external type */
1601 if ((*s2i
->si_init
)(p
) == NOTOK
)
1605 if (!strcasecmp (pm
->pm_name
, "name")) {
1606 e
->eb_name
= pm
->pm_value
;
1609 if (!strcasecmp (pm
->pm_name
, "permission")) {
1610 e
->eb_permission
= pm
->pm_value
;
1613 if (!strcasecmp (pm
->pm_name
, "site")) {
1614 e
->eb_site
= pm
->pm_value
;
1617 if (!strcasecmp (pm
->pm_name
, "directory")) {
1618 e
->eb_dir
= pm
->pm_value
;
1621 if (!strcasecmp (pm
->pm_name
, "mode")) {
1622 e
->eb_mode
= pm
->pm_value
;
1625 if (!strcasecmp (pm
->pm_name
, "size")) {
1626 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1629 if (!strcasecmp (pm
->pm_name
, "server")) {
1630 e
->eb_server
= pm
->pm_value
;
1633 if (!strcasecmp (pm
->pm_name
, "subject")) {
1634 e
->eb_subject
= pm
->pm_value
;
1637 if (!strcasecmp (pm
->pm_name
, "url")) {
1639 * According to RFC 2017, we have to remove all whitespace from
1643 char *u
, *p
= pm
->pm_value
;
1644 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1646 for (; *p
!= '\0'; p
++) {
1647 if (! isspace((unsigned char) *p
))
1654 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1655 e
->eb_body
= getcpy (pm
->pm_value
);
1660 if (!e
->eb_access
) {
1661 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1662 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1675 InitApplication (CT ct
)
1677 CI ci
= &ct
->c_ctinfo
;
1680 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1687 * TRANSFER ENCODINGS
1691 init_encoding (CT ct
, OpenCEFunc openfnx
)
1693 ct
->c_ceopenfnx
= openfnx
;
1694 ct
->c_ceclosefnx
= close_encoding
;
1695 ct
->c_cesizefnx
= size_encoding
;
1702 close_encoding (CT ct
)
1704 CE ce
= &ct
->c_cefile
;
1713 static unsigned long
1714 size_encoding (CT ct
)
1719 CE ce
= &ct
->c_cefile
;
1722 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1723 return (long) st
.st_size
;
1726 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1727 return (long) st
.st_size
;
1731 if (ct
->c_encoding
== CE_EXTERNAL
)
1732 return ct
->c_end
- ct
->c_begin
;
1735 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1736 return ct
->c_end
- ct
->c_begin
;
1738 if (fstat (fd
, &st
) != NOTOK
)
1739 size
= (long) st
.st_size
;
1743 (*ct
->c_ceclosefnx
) (ct
);
1755 return init_encoding (ct
, openBase64
);
1760 openBase64 (CT ct
, char **file
)
1764 bool own_ct_fp
= false;
1765 char *cp
, *buffer
= NULL
;
1766 /* sbeck -- handle suffixes */
1768 CE ce
= &ct
->c_cefile
;
1769 unsigned char *decoded
;
1771 unsigned char digest
[16];
1774 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1779 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1780 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1786 if (*file
== NULL
) {
1789 ce
->ce_file
= mh_xstrdup(*file
);
1793 /* sbeck@cise.ufl.edu -- handle suffixes */
1795 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1796 if (ce
->ce_unlink
) {
1797 /* Create temporary file with filename extension. */
1798 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1799 die("unable to create temporary file in %s",
1803 ce
->ce_file
= add (cp
, ce
->ce_file
);
1805 } else if (*file
== NULL
) {
1807 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1808 die("unable to create temporary file in %s",
1811 ce
->ce_file
= mh_xstrdup(tempfile
);
1814 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1815 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1819 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1820 die("internal error(1)");
1822 buffer
= mh_xmalloc (len
+ 1);
1825 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1826 content_error (ct
->c_file
, ct
, "unable to open for reading");
1832 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1835 switch (cc
= read (fd
, cp
, len
)) {
1837 content_error (ct
->c_file
, ct
, "error reading from");
1841 content_error (NULL
, ct
, "premature eof");
1852 /* decodeBase64() requires null-terminated input. */
1855 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1856 ct
->c_digested
? digest
: NULL
) != OK
)
1861 unsigned char *decoded_p
= decoded
;
1862 for (i
= 0; i
< decoded_len
; ++i
) {
1863 putc (*decoded_p
++, ce
->ce_fp
);
1866 if (ferror (ce
->ce_fp
)) {
1867 content_error (ce
->ce_file
, ct
, "error writing to");
1871 if (ct
->c_digested
) {
1872 if (memcmp(digest
, ct
->c_digest
,
1874 content_error (NULL
, ct
,
1875 "content integrity suspect (digest mismatch) -- continuing");
1878 fprintf (stderr
, "content integrity confirmed\n");
1884 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1886 if (fflush (ce
->ce_fp
)) {
1887 content_error (ce
->ce_file
, ct
, "error writing to");
1891 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1894 *file
= ce
->ce_file
;
1900 return fileno (ce
->ce_fp
);
1907 free_encoding (ct
, 0);
1917 static char hex2nib
[0x80] = {
1918 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1919 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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,
1923 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1924 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1925 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1926 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1927 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1928 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1929 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1930 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1931 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1932 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1933 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1940 return init_encoding (ct
, openQuoted
);
1945 openQuoted (CT ct
, char **file
)
1947 int cc
, digested
, len
, quoted
;
1948 bool own_ct_fp
= false;
1954 CE ce
= &ct
->c_cefile
;
1955 /* sbeck -- handle suffixes */
1960 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1965 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1966 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1972 if (*file
== NULL
) {
1975 ce
->ce_file
= mh_xstrdup(*file
);
1979 /* sbeck@cise.ufl.edu -- handle suffixes */
1981 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1982 if (ce
->ce_unlink
) {
1983 /* Create temporary file with filename extension. */
1984 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1985 die("unable to create temporary file in %s",
1989 ce
->ce_file
= add (cp
, ce
->ce_file
);
1991 } else if (*file
== NULL
) {
1993 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1994 die("unable to create temporary file in %s",
1997 ce
->ce_file
= mh_xstrdup(tempfile
);
2000 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2001 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2005 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2006 die("internal error(2)");
2009 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2010 content_error (ct
->c_file
, ct
, "unable to open for reading");
2016 if ((digested
= ct
->c_digested
))
2017 MD5Init (&mdContext
);
2021 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2023 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2024 content_error (NULL
, ct
, "premature eof");
2028 if ((cc
= gotlen
) > len
)
2032 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2033 if (!isspace ((unsigned char) *ep
))
2038 for (; cp
< ep
; cp
++) {
2040 /* in an escape sequence */
2042 /* at byte 1 of an escape sequence */
2043 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2044 /* next is byte 2 */
2047 /* at byte 2 of an escape sequence */
2049 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2050 putc (mask
, ce
->ce_fp
);
2052 MD5Update (&mdContext
, &mask
, 1);
2053 if (ferror (ce
->ce_fp
)) {
2054 content_error (ce
->ce_file
, ct
, "error writing to");
2057 /* finished escape sequence; next may be literal or a new
2058 * escape sequence */
2061 /* on to next byte */
2065 /* not in an escape sequence */
2067 /* starting an escape sequence, or invalid '='? */
2068 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2069 /* "=\n" soft line break, eat the \n */
2073 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2074 /* We don't have 2 bytes left, so this is an invalid
2075 * escape sequence; just show the raw bytes (below). */
2076 } else if (isxdigit ((unsigned char) cp
[1]) &&
2077 isxdigit ((unsigned char) cp
[2])) {
2078 /* Next 2 bytes are hex digits, making this a valid escape
2079 * sequence; let's decode it (above). */
2083 /* One or both of the next 2 is out of range, making this
2084 * an invalid escape sequence; just show the raw bytes
2088 /* Just show the raw byte. */
2089 putc (*cp
, ce
->ce_fp
);
2092 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2094 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2097 if (ferror (ce
->ce_fp
)) {
2098 content_error (ce
->ce_file
, ct
, "error writing to");
2104 content_error (NULL
, ct
,
2105 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2109 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2111 if (fflush (ce
->ce_fp
)) {
2112 content_error (ce
->ce_file
, ct
, "error writing to");
2117 unsigned char digest
[16];
2119 MD5Final (digest
, &mdContext
);
2120 if (memcmp(digest
, ct
->c_digest
,
2122 content_error (NULL
, ct
,
2123 "content integrity suspect (digest mismatch) -- continuing");
2125 fprintf (stderr
, "content integrity confirmed\n");
2128 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2131 *file
= ce
->ce_file
;
2137 return fileno (ce
->ce_fp
);
2140 free_encoding (ct
, 0);
2157 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2160 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2166 open7Bit (CT ct
, char **file
)
2169 bool own_ct_fp
= false;
2170 char buffer
[BUFSIZ
];
2171 /* sbeck -- handle suffixes */
2174 CE ce
= &ct
->c_cefile
;
2177 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2182 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2183 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2189 if (*file
== NULL
) {
2192 ce
->ce_file
= mh_xstrdup(*file
);
2196 /* sbeck@cise.ufl.edu -- handle suffixes */
2198 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2199 if (ce
->ce_unlink
) {
2200 /* Create temporary file with filename extension. */
2201 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2202 die("unable to create temporary file in %s",
2206 ce
->ce_file
= add (cp
, ce
->ce_file
);
2208 } else if (*file
== NULL
) {
2210 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2211 die("unable to create temporary file in %s",
2214 ce
->ce_file
= mh_xstrdup(tempfile
);
2217 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2218 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2222 if (ct
->c_type
== CT_MULTIPART
) {
2223 CI ci
= &ct
->c_ctinfo
;
2227 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2228 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2229 + 1 + strlen (ci
->ci_subtype
);
2230 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2233 fputs (buffer
, ce
->ce_fp
);
2237 if (ci
->ci_comment
) {
2238 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2239 fputs ("\n\t", ce
->ce_fp
);
2243 putc (' ', ce
->ce_fp
);
2246 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2249 fprintf (ce
->ce_fp
, "\n");
2251 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2253 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2255 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2256 fprintf (ce
->ce_fp
, "\n");
2259 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2260 die("internal error(3)");
2263 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2264 content_error (ct
->c_file
, ct
, "unable to open for reading");
2270 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2272 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2274 content_error (ct
->c_file
, ct
, "error reading from");
2278 content_error (NULL
, ct
, "premature eof");
2286 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2287 advise ("open7Bit", "fwrite");
2289 if (ferror (ce
->ce_fp
)) {
2290 content_error (ce
->ce_file
, ct
, "error writing to");
2295 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2297 if (fflush (ce
->ce_fp
)) {
2298 content_error (ce
->ce_file
, ct
, "error writing to");
2302 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2305 *file
= ce
->ce_file
;
2310 return fileno (ce
->ce_fp
);
2313 free_encoding (ct
, 0);
2327 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2329 char cachefile
[BUFSIZ
];
2332 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2337 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2338 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2344 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2345 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2346 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2347 ce
->ce_file
= mh_xstrdup(cachefile
);
2351 admonish (cachefile
, "unable to fopen for reading");
2354 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2358 *file
= ce
->ce_file
;
2359 *fd
= fileno (ce
->ce_fp
);
2370 return init_encoding (ct
, openFile
);
2375 openFile (CT ct
, char **file
)
2378 char cachefile
[BUFSIZ
];
2379 struct exbody
*e
= ct
->c_ctexbody
;
2380 CE ce
= &ct
->c_cefile
;
2382 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2394 content_error (NULL
, ct
, "missing name parameter");
2398 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2401 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2402 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2406 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2407 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2408 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2412 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2413 if ((fp
= fopen (cachefile
, "w"))) {
2415 char buffer
[BUFSIZ
];
2416 FILE *gp
= ce
->ce_fp
;
2418 fseek (gp
, 0L, SEEK_SET
);
2420 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2422 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2423 advise ("openFile", "fwrite");
2428 admonish (ce
->ce_file
, "error reading");
2429 (void) m_unlink (cachefile
);
2430 } else if (ferror (fp
)) {
2431 admonish (cachefile
, "error writing");
2432 (void) m_unlink (cachefile
);
2439 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2440 *file
= ce
->ce_file
;
2441 return fileno (ce
->ce_fp
);
2451 return init_encoding (ct
, openFTP
);
2456 openFTP (CT ct
, char **file
)
2462 char *bp
, *ftp
, *user
, *pass
;
2463 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2465 CE ce
= &ct
->c_cefile
;
2466 static char *username
= NULL
;
2467 static char *password
= NULL
;
2471 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2477 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2488 if (!e
->eb_name
|| !e
->eb_site
) {
2489 content_error (NULL
, ct
, "missing %s parameter",
2490 e
->eb_name
? "site": "name");
2494 /* Get the buffer ready to go */
2496 buflen
= sizeof(buffer
);
2499 * Construct the query message for user
2501 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2507 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2513 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2514 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2519 if (e
->eb_size
> 0) {
2520 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2525 snprintf (bp
, buflen
, "? ");
2528 * Now, check the answer
2530 if (!read_yes_or_no_if_tty (buffer
))
2535 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2539 ruserpass (e
->eb_site
, &username
, &password
, 0);
2544 ce
->ce_unlink
= (*file
== NULL
);
2546 cachefile
[0] = '\0';
2547 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2548 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2549 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2550 if (*file
== NULL
) {
2557 ce
->ce_file
= mh_xstrdup(*file
);
2559 ce
->ce_file
= mh_xstrdup(cachefile
);
2562 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2563 die("unable to create temporary file in %s",
2566 ce
->ce_file
= mh_xstrdup(tempfile
);
2569 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2570 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2579 vec
[vecp
++] = r1bindex (ftp
, '/');
2580 vec
[vecp
++] = e
->eb_site
;
2583 vec
[vecp
++] = e
->eb_dir
;
2584 vec
[vecp
++] = e
->eb_name
;
2585 vec
[vecp
++] = ce
->ce_file
,
2586 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2587 ? "ascii" : "binary";
2595 adios ("fork", "unable to");
2599 close (fileno (ce
->ce_fp
));
2601 fprintf (stderr
, "unable to exec ");
2607 if (pidXwait (child_id
, NULL
)) {
2608 username
= password
= NULL
;
2618 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2623 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2624 if ((fp
= fopen (cachefile
, "w"))) {
2626 FILE *gp
= ce
->ce_fp
;
2628 fseek (gp
, 0L, SEEK_SET
);
2630 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2632 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2633 advise ("openFTP", "fwrite");
2638 admonish (ce
->ce_file
, "error reading");
2639 (void) m_unlink (cachefile
);
2640 } else if (ferror (fp
)) {
2641 admonish (cachefile
, "error writing");
2642 (void) m_unlink (cachefile
);
2650 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2651 *file
= ce
->ce_file
;
2652 return fileno (ce
->ce_fp
);
2663 return init_encoding (ct
, openMail
);
2668 openMail (CT ct
, char **file
)
2670 int child_id
, fd
, vecp
;
2672 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2673 struct exbody
*e
= ct
->c_ctexbody
;
2674 CE ce
= &ct
->c_cefile
;
2676 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2687 if (!e
->eb_server
) {
2688 content_error (NULL
, ct
, "missing server parameter");
2692 /* Get buffer ready to go */
2694 buflen
= sizeof(buffer
);
2696 /* Now, construct query message */
2697 snprintf (bp
, buflen
, "Retrieve content");
2703 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2709 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2711 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2713 /* Now, check answer */
2714 if (!read_yes_or_no_if_tty (buffer
))
2718 vec
[vecp
++] = r1bindex (mailproc
, '/');
2719 vec
[vecp
++] = e
->eb_server
;
2720 vec
[vecp
++] = "-subject";
2721 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2722 vec
[vecp
++] = "-body";
2723 vec
[vecp
++] = e
->eb_body
;
2729 advise ("fork", "unable to");
2733 execvp (mailproc
, vec
);
2734 fprintf (stderr
, "unable to exec ");
2740 if (pidXwait (child_id
, NULL
) == OK
)
2741 inform("request sent");
2745 if (*file
== NULL
) {
2747 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2748 die("unable to create temporary file in %s",
2751 ce
->ce_file
= mh_xstrdup(tempfile
);
2754 ce
->ce_file
= mh_xstrdup(*file
);
2758 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2759 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2763 /* showproc is for mhshow and mhstore, though mhlist -debug
2764 * prints it, too. */
2765 free(ct
->c_showproc
);
2766 ct
->c_showproc
= mh_xstrdup("true");
2768 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2769 *file
= ce
->ce_file
;
2770 return fileno (ce
->ce_fp
);
2781 return init_encoding (ct
, openURL
);
2786 openURL (CT ct
, char **file
)
2788 struct exbody
*e
= ct
->c_ctexbody
;
2789 CE ce
= &ct
->c_cefile
;
2790 char *urlprog
, *program
;
2791 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2795 struct msgs_array args
= { 0, 0, NULL
};
2798 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2802 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2806 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2818 content_error(NULL
, ct
, "missing url parameter");
2822 ce
->ce_unlink
= (*file
== NULL
);
2824 cachefile
[0] = '\0';
2826 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2827 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2828 if (*file
== NULL
) {
2835 ce
->ce_file
= mh_xstrdup(*file
);
2837 ce
->ce_file
= mh_xstrdup(cachefile
);
2840 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2841 die("unable to create temporary file in %s",
2844 ce
->ce_file
= mh_xstrdup(tempfile
);
2847 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2848 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2852 switch (child_id
= fork()) {
2854 adios ("fork", "unable to");
2858 argsplit_msgarg(&args
, urlprog
, &program
);
2859 app_msgarg(&args
, e
->eb_url
);
2860 app_msgarg(&args
, NULL
);
2861 dup2(fileno(ce
->ce_fp
), 1);
2862 close(fileno(ce
->ce_fp
));
2863 execvp(program
, args
.msgs
);
2864 fprintf(stderr
, "Unable to exec ");
2870 if (pidXwait(child_id
, NULL
)) {
2878 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2883 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2884 if ((fp
= fopen(cachefile
, "w"))) {
2886 FILE *gp
= ce
->ce_fp
;
2888 fseeko(gp
, 0, SEEK_SET
);
2890 while ((cc
= fread(buffer
, sizeof(*buffer
),
2891 sizeof(buffer
), gp
)) > 0)
2892 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2893 advise ("openURL", "fwrite");
2899 admonish(ce
->ce_file
, "error reading");
2900 (void) m_unlink (cachefile
);
2907 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2908 *file
= ce
->ce_file
;
2909 return fileno(ce
->ce_fp
);
2914 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2915 * has to be base64 decoded.
2918 readDigest (CT ct
, char *cp
)
2920 unsigned char *digest
;
2923 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2924 const size_t maxlen
= sizeof ct
->c_digest
;
2926 if (strlen ((char *) digest
) <= maxlen
) {
2927 memcpy (ct
->c_digest
, digest
, maxlen
);
2932 fprintf (stderr
, "MD5 digest=");
2933 for (i
= 0; i
< maxlen
; ++i
) {
2934 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2936 fprintf (stderr
, "\n");
2942 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2943 (int) strlen ((char *) digest
));
2953 /* Multipart parts might have content before the first subpart and/or
2954 after the last subpart that hasn't been stored anywhere else, so do
2957 get_leftover_mp_content (CT ct
, int before
/* or after */)
2959 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2961 bool found_boundary
= false;
2967 char *content
= NULL
;
2969 if (! m
) return NOTOK
;
2972 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2974 /* Isolate the beginning of this part to the beginning of the
2975 first subpart and save any content between them. */
2976 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2977 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2978 boundary
= concat ("--", m
->mp_start
, NULL
);
2980 struct part
*last_subpart
= NULL
;
2981 struct part
*subpart
;
2983 /* Go to the last subpart to get its end position. */
2984 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2985 last_subpart
= subpart
;
2988 if (last_subpart
== NULL
) return NOTOK
;
2990 /* Isolate the end of the last subpart to the end of this part
2991 and save any content between them. */
2992 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2993 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2994 boundary
= concat ("--", m
->mp_stop
, NULL
);
2997 /* Back up by 1 to pick up the newline. */
2998 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
3000 /* Don't look beyond beginning of first subpart (before) or
3001 next part (after). */
3002 if (read
> max
) bufp
[read
-max
] = '\0';
3005 if (! strcmp (bufp
, boundary
)) {
3006 found_boundary
= true;
3009 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3010 found_boundary
= true;
3015 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3017 char *old_content
= content
;
3018 content
= concat (content
, bufp
, NULL
);
3022 ? concat ("\n", bufp
, NULL
)
3023 : concat (bufp
, NULL
);
3028 if (found_boundary
|| read
> max
) break;
3030 if (read
> max
) break;
3034 /* Skip the newline if that's all there is. */
3038 /* Remove trailing newline, except at EOF. */
3039 if ((before
|| ! feof (ct
->c_fp
)) &&
3040 (cp
= content
+ strlen (content
)) > content
&&
3045 if (strlen (content
) > 1) {
3047 m
->mp_content_before
= content
;
3049 m
->mp_content_after
= content
;
3064 ct_type_str (int type
)
3067 case CT_APPLICATION
:
3068 return "application";
3084 return "unknown_type";
3090 ct_subtype_str (int type
, int subtype
)
3093 case CT_APPLICATION
:
3095 case APPLICATION_OCTETS
:
3097 case APPLICATION_POSTSCRIPT
:
3098 return "postscript";
3100 return "unknown_app_subtype";
3104 case MESSAGE_RFC822
:
3106 case MESSAGE_PARTIAL
:
3108 case MESSAGE_EXTERNAL
:
3111 return "unknown_msg_subtype";
3117 case MULTI_ALTERNATE
:
3118 return "alternative";
3121 case MULTI_PARALLEL
:
3126 return "unknown_multipart_subtype";
3137 return "unknown_text_subtype";
3140 return "unknown_type";
3146 ct_str_type (const char *type
)
3148 struct str2init
*s2i
;
3150 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3151 if (! strcasecmp (type
, s2i
->si_key
)) {
3155 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3164 ct_str_subtype (int type
, const char *subtype
)
3169 case CT_APPLICATION
:
3170 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3171 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3175 return kv
->kv_value
;
3177 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3178 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3182 return kv
->kv_value
;
3184 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3185 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3189 return kv
->kv_value
;
3191 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3192 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3196 return kv
->kv_value
;
3203 /* Find the content type and InitFunc for the CT. */
3204 const struct str2init
*
3205 get_ct_init (int type
)
3207 const struct str2init
*sp
;
3209 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3210 if (type
== sp
->si_val
) {
3219 ce_str (int encoding
)
3225 return "quoted-printable";
3241 /* Find the content type and InitFunc for the content encoding method. */
3242 const struct str2init
*
3243 get_ce_method (const char *method
)
3245 struct str2init
*sp
;
3247 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3248 if (! strcasecmp (method
, sp
->si_key
)) {
3257 * Parse a series of MIME attributes (or parameters) given a header as
3260 * Arguments include:
3262 * filename - Name of input file (for error messages)
3263 * fieldname - Name of field being processed
3264 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3265 * Updated to point to end of attributes when finished.
3266 * param_head - Pointer to head of parameter list
3267 * param_tail - Pointer to tail of parameter list
3268 * commentp - Pointer to header comment pointer (may be NULL)
3270 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3271 * DONE to indicate a benign error (minor parsing error, but the program
3276 parse_header_attrs (const char *filename
, const char *fieldname
,
3277 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3280 char *cp
= *header_attrp
;
3286 struct sectlist
*next
;
3292 struct sectlist
*sechead
;
3293 struct parmlist
*next
;
3294 } *pp
, *pp2
, *phead
= NULL
;
3296 while (*cp
== ';') {
3297 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3298 bool encoded
= false;
3299 bool partial
= false;
3300 int len
= 0, index
= 0;
3303 while (isspace ((unsigned char) *cp
))
3307 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3312 if (! suppress_extraneous_trailing_semicolon_warning
) {
3313 inform("extraneous trailing ';' in message %s's %s: "
3314 "parameter list", filename
, fieldname
);
3319 /* down case the attribute name */
3320 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3321 *dp
= tolower ((unsigned char) *dp
);
3323 for (up
= dp
; isspace ((unsigned char) *dp
);)
3325 if (dp
== cp
|| *dp
!= '=') {
3326 inform("invalid parameter in message %s's %s: field\n"
3327 " parameter %s (error detected at offset %ld)",
3328 filename
, fieldname
, cp
, (long)(dp
- cp
));
3333 * To handle RFC 2231, we have to deal with the following extensions:
3335 * name*=encoded-value
3336 * name*<N>=part-N-of-a-parameter-value
3337 * name*<N>*=encoded-part-N-of-a-parameter-value
3340 * If there's a * right before the equal sign, it's encoded.
3341 * If there's a * and one or more digits, then it's section N.
3343 * Remember we can have one or the other, or both. cp points to
3344 * beginning of name, up points past the last character in the
3348 for (vp
= cp
; vp
< up
; vp
++) {
3349 if (*vp
== '*' && vp
< up
- 1) {
3353 if (*vp
== '*' && vp
== up
- 1) {
3355 } else if (partial
) {
3356 if (isdigit((unsigned char) *vp
))
3357 index
= *vp
- '0' + index
* 10;
3359 inform("invalid parameter index in message %s's %s: field"
3360 "\n (parameter %s)", filename
, fieldname
, cp
);
3369 * Break out the parameter name and value sections and allocate
3373 nameptr
= mh_xmalloc(len
+ 1);
3374 strncpy(nameptr
, cp
, len
);
3375 nameptr
[len
] = '\0';
3377 for (dp
++; isspace ((unsigned char) *dp
);)
3382 * Single quotes delimit the character set and language tag.
3383 * They are required on the first section (or a complete
3388 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3394 charset
= mh_xmalloc(len
+ 1);
3395 strncpy(charset
, dp
, len
);
3396 charset
[len
] = '\0';
3402 inform("missing charset in message %s's %s: field\n"
3403 " (parameter %s)", filename
, fieldname
, nameptr
);
3409 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3416 lang
= mh_xmalloc(len
+ 1);
3417 strncpy(lang
, dp
, len
);
3424 inform("missing language tag in message %s's %s: field\n"
3425 " (parameter %s)", filename
, fieldname
, nameptr
);
3435 * At this point vp should be pointing at the beginning
3436 * of the encoded value/section. Continue until we reach
3437 * the end or get whitespace. But first, calculate the
3438 * length so we can allocate the correct buffer size.
3441 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3443 if (*(vp
+ 1) == '\0' ||
3444 !isxdigit((unsigned char) *(vp
+ 1)) ||
3445 *(vp
+ 2) == '\0' ||
3446 !isxdigit((unsigned char) *(vp
+ 2))) {
3447 inform("invalid encoded sequence in message %s's %s: field\n"
3448 " (parameter %s)", filename
, fieldname
, nameptr
);
3459 up
= valptr
= mh_xmalloc(len
+ 1);
3461 for (vp
= dp
; istoken(*vp
); vp
++) {
3463 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3474 * A "normal" string. If it's got a leading quote, then we
3475 * strip the quotes out. Otherwise go until we reach the end
3476 * or get whitespace. Note we scan it twice; once to get the
3477 * length, then the second time copies it into the destination
3484 for (cp
= dp
+ 1;;) {
3488 inform("invalid quoted-string in message %s's %s: field\n"
3489 " (parameter %s)", filename
, fieldname
, nameptr
);
3509 for (cp
= dp
; istoken (*cp
); cp
++) {
3514 valptr
= mh_xmalloc(len
+ 1);
3518 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3526 strncpy(valptr
, cp
= dp
, len
);
3534 * If 'partial' is set, we don't allocate a parameter now. We
3535 * put it on the parameter linked list to be reassembled later.
3537 * "phead" points to a list of all parameters we need to reassemble.
3538 * Each parameter has a list of sections. We insert the sections in
3543 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3544 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3559 * Insert this into the section linked list
3567 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3568 sp
->next
= pp
->sechead
;
3571 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3572 if (sp2
->index
== sp
->index
) {
3573 inform("duplicate index (%d) in message %s's %s: field"
3574 "\n (parameter %s)", sp
->index
, filename
,
3575 fieldname
, nameptr
);
3578 if (sp2
->index
< sp
->index
&&
3579 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3580 sp
->next
= sp2
->next
;
3587 inform("Internal error: cannot insert partial param "
3588 "in message %s's %s: field\n (parameter %s)",
3589 filename
, fieldname
, nameptr
);
3595 * Save our charset and lang tags.
3598 if (index
== 0 && encoded
) {
3600 pp
->charset
= charset
;
3605 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3606 pm
->pm_charset
= charset
;
3610 while (isspace ((unsigned char) *cp
))
3614 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3620 * Now that we're done, reassemble all of the partial parameters.
3623 for (pp
= phead
; pp
!= NULL
; ) {
3627 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3628 if (sp
->index
!= pindex
++) {
3629 inform("missing section %d for parameter in message "
3630 "%s's %s: field\n (parameter %s)", pindex
- 1,
3631 filename
, fieldname
, pp
->name
);
3637 p
= q
= mh_xmalloc(tlen
+ 1);
3638 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3639 memcpy(q
, sp
->value
, sp
->len
);
3649 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3650 pm
->pm_charset
= pp
->charset
;
3651 pm
->pm_lang
= pp
->lang
;
3662 * Return the charset for a particular content type.
3666 content_charset (CT ct
)
3668 char *ret_charset
= NULL
;
3670 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3672 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3677 * Create a string based on a list of output parameters. Assume that this
3678 * parameter string will be appended to an existing header, so start out
3679 * with the separator (;). Perform RFC 2231 encoding when necessary.
3683 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3685 char *paramout
= NULL
;
3686 char line
[CPERLIN
* 2], *q
;
3687 int curlen
, index
, cont
, encode
, i
;
3688 size_t valoff
, numchars
;
3690 while (params
!= NULL
) {
3696 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3699 if (strlen(params
->pm_name
) > CPERLIN
) {
3700 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3705 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3708 * Loop until we get a parameter that fits within a line. We
3709 * assume new lines start with a tab, so check our overflow based
3719 * At this point we're definitely continuing the line, so
3720 * be sure to include the parameter name and section index.
3723 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3724 params
->pm_name
, index
);
3727 * Both of these functions do a NUL termination
3731 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3732 numchars
, valoff
, index
);
3734 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3744 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3749 * "line" starts with a ;\n\t, so that doesn't count against
3750 * the length. But add 8 since it starts with a tab; that's
3751 * how we end up with 5.
3754 initialwidth
= strlen(line
) + 5;
3757 * At this point the line should be built, so add it to our
3758 * current output buffer.
3761 paramout
= add(line
, paramout
);
3765 * If this won't fit on the line, start a new one. Save room in
3766 * case we need a semicolon on the end
3769 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3781 * At this point, we're either finishing a continued parameter, or
3782 * we're working on a new one.
3786 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3787 params
->pm_name
, index
);
3789 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3794 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3795 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3797 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3798 strlen(params
->pm_value
+ valoff
), valoff
);
3805 paramout
= add(line
, paramout
);
3806 initialwidth
+= strlen(line
);
3808 params
= params
->pm_next
;
3812 *offsetout
= initialwidth
;
3818 * Calculate the size of a parameter.
3822 * pm - The parameter being output
3823 * index - If continuing the parameter, the index of the section
3825 * valueoff - The current offset into the parameter value that we're
3826 * working on (previous sections have consumed valueoff bytes).
3827 * encode - Set if we should perform encoding on this parameter section
3828 * (given that we're consuming bytesfit bytes).
3829 * cont - Set if the remaining data in value will not fit on a single
3830 * line and will need to be continued.
3831 * bytesfit - The number of bytes that we can consume from the parameter
3832 * value and still fit on a completely new line. The
3833 * calculation assumes the new line starts with a tab,
3834 * includes the parameter name and any encoding, and fits
3835 * within CPERLIN bytes. Will always be at least 1.
3839 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3842 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3843 size_t len
= 0, fit
= 0;
3844 int fitlimit
= 0, eightbit
, maxfit
;
3849 * Add up the length. First, start with the parameter name.
3852 len
= strlen(pm
->pm_name
);
3855 * Scan the parameter value and see if we need to do encoding for this
3859 eightbit
= contains8bit(start
, NULL
);
3862 * Determine if we need to encode this section. Encoding is necessary if:
3864 * - There are any 8-bit characters at all and we're on the first
3866 * - There are 8-bit characters within N bytes of our section start.
3867 * N is calculated based on the number of bytes it would take to
3868 * reach CPERLIN. Specifically:
3869 * 8 (starting tab) +
3870 * strlen(param name) +
3871 * 4 ('* for section marker, '=', opening/closing '"')
3873 * is the number of bytes used by everything that isn't part of the
3874 * value. So that gets subtracted from CPERLIN.
3877 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3878 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3879 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3883 len
++; /* Add in equal sign */
3887 * We're using maxfit as a marker for how many characters we can
3888 * fit into the line. Bump it by two because we're not using quotes
3895 * If we don't have a charset or language tag in this parameter,
3899 if (! pm
->pm_charset
) {
3900 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3901 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3902 die("8-bit characters in parameter \"%s\", but "
3903 "local character set is US-ASCII", pm
->pm_name
);
3906 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3908 len
++; /* For the encoding marker */
3911 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3916 * We know we definitely need to include an index. maxfit already
3917 * includes the section marker.
3919 len
+= strlen(indexchar
);
3921 for (p
= start
; *p
!= '\0'; p
++) {
3922 if (isparamencode(*p
)) {
3930 * Just so there's no confusion: maxfit is counting OUTPUT
3931 * characters (post-encoding). fit is counting INPUT characters.
3933 if (! fitlimit
&& maxfit
>= 0)
3935 else if (! fitlimit
)
3940 * Calculate the string length, but add room for quoting \
3941 * and " if necessary. Also account for quotes at beginning
3944 for (p
= start
; *p
!= '\0'; p
++) {
3955 if (! fitlimit
&& maxfit
>= 0)
3957 else if (! fitlimit
)
3974 * Output an encoded parameter string.
3978 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3979 size_t valueoff
, int index
)
3981 size_t outlen
= 0, n
;
3982 char *endptr
= output
+ len
, *p
;
3985 * First, output the marker for an encoded string.
3993 * If the index is 0, output the character set and language tag.
3994 * If theses were NULL, they should have already been filled in
3999 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
4003 if (output
> endptr
) {
4004 inform("Internal error: parameter buffer overflow");
4010 * Copy over the value, encoding if necessary
4013 p
= pm
->pm_value
+ valueoff
;
4014 while (valuelen
-- > 0) {
4015 if (isparamencode(*p
)) {
4016 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4023 if (output
> endptr
) {
4024 inform("Internal error: parameter buffer overflow");
4035 * Output a "normal" parameter, without encoding. Be sure to escape
4036 * quotes and backslashes if necessary.
4040 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4044 char *endptr
= output
+ len
, *p
;
4050 p
= pm
->pm_value
+ valueoff
;
4052 while (valuelen
-- > 0) {
4063 if (output
> endptr
) {
4064 inform("Internal error: parameter buffer overflow");
4069 if (output
- 2 > endptr
) {
4070 inform("Internal error: parameter buffer overflow");
4081 * Add a parameter to the parameter linked list
4085 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4090 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4091 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4094 (*last
)->pm_next
= pm
;
4105 * Either replace a current parameter with a new value, or add the parameter
4106 * to the parameter linked list.
4110 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4114 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4115 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4117 * If nocopy is set, it's assumed that we own both name
4118 * and value. We don't need name, so we discard it now.
4123 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4128 return add_param(first
, last
, name
, value
, nocopy
);
4132 * Retrieve a parameter value from a parameter linked list. If the parameter
4133 * value needs converted to the local character set, do that now.
4137 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4139 while (first
!= NULL
) {
4140 if (strcasecmp(name
, first
->pm_name
) == 0) {
4142 return first
->pm_value
;
4143 return getcpy(get_param_value(first
, replace
));
4145 first
= first
->pm_next
;
4152 * Return a parameter value, converting to the local character set if
4157 get_param_value(PM pm
, char replace
)
4159 static char buffer
[4096]; /* I hope no parameters are larger */
4160 size_t bufsize
= sizeof(buffer
);
4165 ICONV_CONST
char *p
;
4166 #else /* HAVE_ICONV */
4168 #endif /* HAVE_ICONV */
4173 * If we don't have a character set indicated, it's assumed to be
4174 * US-ASCII. If it matches our character set, we don't need to convert
4178 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4179 strlen(pm
->pm_charset
))) {
4180 return pm
->pm_value
;
4184 * In this case, we need to convert. If we have iconv support, use
4185 * that. Otherwise, go through and simply replace every non-ASCII
4186 * character with the substitution character.
4191 bufsize
= sizeof(buffer
);
4192 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4194 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4195 if (cd
== (iconv_t
) -1) {
4199 inbytes
= strlen(pm
->pm_value
);
4203 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4204 if (errno
!= EILSEQ
) {
4209 * Reset shift state, substitute our character,
4210 * try to restart conversion.
4213 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4226 for (++p
, --inbytes
;
4227 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4246 #endif /* HAVE_ICONV */
4249 * Take everything non-ASCII and substitute the replacement character
4253 bufsize
= sizeof(buffer
);
4254 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4255 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))