1 /* mhparse.c -- routines to parse the contents of MIME messages
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
14 #include <h/mhparse.h>
17 #include <h/mhcachesbr.h>
18 #include "sbr/m_mktemp.h"
22 #endif /* HAVE_ICONV */
23 #include "sbr/base64.h"
28 int checksw
= 0; /* check Content-MD5 field */
31 * These are for mhfixmsg to:
32 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
34 * 2) Suppress the warning about bogus multipart content, and report it.
35 * 3) Suppress the warning about extraneous trailing ';' in header parameter
38 bool skip_mp_cte_check
;
39 bool suppress_bogus_mp_content_warning
;
40 bool bogus_mp_content
;
41 bool suppress_extraneous_trailing_semicolon_warning
;
44 * By default, suppress warning about multiple MIME-Version header fields.
46 bool suppress_multiple_mime_version_warning
= true;
48 /* list of preferred type/subtype pairs, for -prefer */
49 mime_type_subtype mime_preference
[NPREFS
];
54 * Structures for TEXT messages
56 struct k2v SubText
[] = {
57 { "plain", TEXT_PLAIN
},
58 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
59 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
60 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
63 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
66 * Structures for MULTIPART messages
68 struct k2v SubMultiPart
[] = {
69 { "mixed", MULTI_MIXED
},
70 { "alternative", MULTI_ALTERNATE
},
71 { "digest", MULTI_DIGEST
},
72 { "parallel", MULTI_PARALLEL
},
73 { "related", MULTI_RELATED
},
74 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
78 * Structures for MESSAGE messages
80 struct k2v SubMessage
[] = {
81 { "rfc822", MESSAGE_RFC822
},
82 { "partial", MESSAGE_PARTIAL
},
83 { "external-body", MESSAGE_EXTERNAL
},
84 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
88 * Structure for APPLICATION messages
90 struct k2v SubApplication
[] = {
91 { "octet-stream", APPLICATION_OCTETS
},
92 { "postscript", APPLICATION_POSTSCRIPT
},
93 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
97 * Mapping of names of CTE types in mhbuild directives
99 static struct k2v EncodingType
[] = {
102 { "q-p", CE_QUOTED
},
103 { "quoted-printable", CE_QUOTED
},
104 { "b64", CE_BASE64
},
105 { "base64", CE_BASE64
},
113 static CT
get_content (FILE *, char *, int);
114 static int get_comment (const char *, const char *, char **, char **);
116 static int InitGeneric (CT
);
117 static int InitText (CT
);
118 static int InitMultiPart (CT
);
119 static void reverse_parts (CT
);
120 static void prefer_parts(CT ct
);
121 static int InitMessage (CT
);
122 static int InitApplication (CT
);
123 static int init_encoding (CT
, OpenCEFunc
);
124 static unsigned long size_encoding (CT
);
125 static int InitBase64 (CT
);
126 static int openBase64 (CT
, char **);
127 static int InitQuoted (CT
);
128 static int openQuoted (CT
, char **);
129 static int Init7Bit (CT
);
130 static int openExternal (CT
, CT
, CE
, char **, int *);
131 static int InitFile (CT
);
132 static int openFile (CT
, char **);
133 static int InitFTP (CT
);
134 static int openFTP (CT
, char **);
135 static int InitMail (CT
);
136 static int openMail (CT
, char **);
137 static int readDigest (CT
, char *);
138 static int get_leftover_mp_content (CT
, int);
139 static int InitURL (CT
);
140 static int openURL (CT
, char **);
141 static int parse_header_attrs (const char *, const char *, char **, PM
*,
143 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
144 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
145 static int get_dispo (char *, CT
, int);
147 struct str2init str2cts
[] = {
148 { "application", CT_APPLICATION
, InitApplication
},
149 { "audio", CT_AUDIO
, InitGeneric
},
150 { "image", CT_IMAGE
, InitGeneric
},
151 { "message", CT_MESSAGE
, InitMessage
},
152 { "multipart", CT_MULTIPART
, InitMultiPart
},
153 { "text", CT_TEXT
, InitText
},
154 { "video", CT_VIDEO
, InitGeneric
},
155 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
156 { NULL
, CT_UNKNOWN
, NULL
},
159 struct str2init str2ces
[] = {
160 { "base64", CE_BASE64
, InitBase64
},
161 { "quoted-printable", CE_QUOTED
, InitQuoted
},
162 { "8bit", CE_8BIT
, Init7Bit
},
163 { "7bit", CE_7BIT
, Init7Bit
},
164 { "binary", CE_BINARY
, Init7Bit
},
165 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
166 { NULL
, CE_UNKNOWN
, NULL
},
170 * NOTE WELL: si_key MUST NOT have value of NOTOK
172 * si_val is 1 if access method is anonymous.
174 struct str2init str2methods
[] = {
175 { "afs", 1, InitFile
},
176 { "anon-ftp", 1, InitFTP
},
177 { "ftp", 0, InitFTP
},
178 { "local-file", 0, InitFile
},
179 { "mail-server", 0, InitMail
},
180 { "url", 0, InitURL
},
186 * Main entry point for parsing a MIME message or file.
187 * It returns the Content structure for the top level
188 * entity in the file.
192 parse_mime (char *file
)
201 bogus_mp_content
= false;
204 * Check if file is actually standard input
206 if ((is_stdin
= !(strcmp (file
, "-")))) {
207 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
209 advise("mhparse", "unable to create temporary file in %s",
213 file
= mh_xstrdup(tfile
);
215 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
216 if (fwrite(buffer
, 1, n
, fp
) != n
) {
217 (void) m_unlink (file
);
218 advise (file
, "error copying to temporary file");
224 if (ferror (stdin
)) {
225 (void) m_unlink (file
);
226 advise ("stdin", "error reading");
230 (void) m_unlink (file
);
231 advise (file
, "error writing");
234 fseek (fp
, 0L, SEEK_SET
);
235 } else if (stat (file
, &statbuf
) == NOTOK
) {
236 advise (file
, "unable to stat");
238 } else if (S_ISDIR(statbuf
.st_mode
)) {
239 /* Don't try to parse a directory. */
240 inform("%s is a directory", file
);
242 } else if ((fp
= fopen (file
, "r")) == NULL
) {
243 advise (file
, "unable to read");
247 if (!(ct
= get_content (fp
, file
, 1))) {
249 (void) m_unlink (file
);
250 inform("unable to decode %s", file
);
255 ct
->c_unlink
= 1; /* temp file to remove */
259 if (ct
->c_end
== 0L) {
260 fseek (fp
, 0L, SEEK_END
);
261 ct
->c_end
= ftell (fp
);
264 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
276 * Main routine for reading/parsing the headers
277 * of a message content.
279 * toplevel = 1 # we are at the top level of the message
280 * toplevel = 0 # we are inside message type or multipart type
281 * # other than multipart/digest
282 * toplevel = -1 # we are inside multipart/digest
283 * NB: on failure we will fclose(in)!
287 get_content (FILE *in
, char *file
, int toplevel
)
290 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
294 m_getfld_state_t gstate
;
296 /* allocate the content structure */
299 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
300 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
303 * Parse the header fields for this
304 * content into a linked list.
306 gstate
= m_getfld_state_init(in
);
307 m_getfld_track_filepos2(&gstate
);
308 for (compnum
= 1;;) {
309 int bufsz
= sizeof buf
;
310 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
315 /* get copies of the buffers */
316 np
= mh_xstrdup(name
);
317 vp
= mh_xstrdup(buf
);
319 /* if necessary, get rest of field */
320 while (state
== FLDPLUS
) {
322 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
323 vp
= add (buf
, vp
); /* add to previous value */
326 /* Now add the header data to the list */
327 add_header (ct
, np
, vp
);
329 /* continue, to see if this isn't the last header field */
330 ct
->c_begin
= ftell (in
) + 1;
334 /* There are two cases. The unusual one is when there is no
335 * blank line between the headers and the body. This is
336 * indicated by the name of the header starting with `:'.
338 * For both cases, normal first, `1' is the desired c_begin
339 * file position for the start of the body, and `2' is the
340 * file position when buf is returned.
342 * f o o : b a r \n \n b o d y \n bufsz = 6
344 * f o o : b a r \n b o d y \n bufsz = 4
347 * For the normal case, bufsz includes the
348 * header-terminating `\n', even though it is not in buf,
349 * but bufsz isn't affected when it's missing in the unusual
351 if (name
[0] == ':') {
352 ct
->c_begin
= ftell(in
) - bufsz
;
354 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
359 ct
->c_begin
= ftell (in
);
364 die("message format error in component #%d", compnum
);
367 die("getfld() returned %d", state
);
370 /* break out of the loop */
373 m_getfld_state_destroy (&gstate
);
376 * Read the content headers. We will parse the
377 * MIME related header fields into their various
378 * structures and set internal flags related to
379 * content type/subtype, etc.
382 hp
= ct
->c_first_hf
; /* start at first header field */
384 /* Get MIME-Version field */
385 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
390 vrsn
= mh_xstrdup(FENDNULL(hp
->value
));
392 /* Now, cleanup this field */
395 while (isspace ((unsigned char) *cp
))
397 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
399 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
400 if (!isspace ((unsigned char) *dp
))
404 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
407 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
410 for (dp
= cp
; istoken (*dp
); dp
++)
414 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
417 inform("message %s has unknown value for %s: field (%s), continuing...",
418 ct
->c_file
, VRSN_FIELD
, cp
);
423 if (! suppress_multiple_mime_version_warning
)
424 inform("message %s has multiple %s: fields",
425 ct
->c_file
, VRSN_FIELD
);
429 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
430 /* Get Content-Type field */
431 struct str2init
*s2i
;
432 CI ci
= &ct
->c_ctinfo
;
434 /* Check if we've already seen a Content-Type header */
436 inform("message %s has multiple %s: fields",
437 ct
->c_file
, TYPE_FIELD
);
441 /* Parse the Content-Type field */
442 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
446 * Set the Init function and the internal
447 * flag for this content type.
449 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
450 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
452 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
454 ct
->c_type
= s2i
->si_val
;
455 ct
->c_ctinitfnx
= s2i
->si_init
;
457 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
458 /* Get Content-Transfer-Encoding field */
460 struct str2init
*s2i
;
463 * Check if we've already seen the
464 * Content-Transfer-Encoding field
467 inform("message %s has multiple %s: fields",
468 ct
->c_file
, ENCODING_FIELD
);
472 /* get copy of this field */
473 ct
->c_celine
= cp
= mh_xstrdup(FENDNULL(hp
->value
));
475 while (isspace ((unsigned char) *cp
))
477 for (dp
= cp
; istoken (*dp
); dp
++)
483 * Find the internal flag and Init function
484 * for this transfer encoding.
486 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
487 if (!strcasecmp (cp
, s2i
->si_key
))
489 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
492 ct
->c_encoding
= s2i
->si_val
;
494 /* Call the Init function for this encoding */
495 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
498 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
499 /* Get Content-MD5 field */
505 if (ct
->c_digested
) {
506 inform("message %s has multiple %s: fields",
507 ct
->c_file
, MD5_FIELD
);
511 ep
= cp
= mh_xstrdup(FENDNULL(hp
->value
)); /* get a copy */
513 while (isspace ((unsigned char) *cp
))
515 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
517 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
518 if (!isspace ((unsigned char) *dp
))
522 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
525 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
530 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
538 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
539 /* Get Content-ID field */
540 ct
->c_id
= add (hp
->value
, ct
->c_id
);
542 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
543 /* Get Content-Description field */
544 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
546 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
547 /* Get Content-Disposition field */
548 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
553 hp
= hp
->next
; /* next header field */
557 * Check if we saw a Content-Type field.
558 * If not, then assign a default value for
559 * it, and the Init function.
563 * If we are inside a multipart/digest message,
564 * so default type is message/rfc822
567 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
569 ct
->c_type
= CT_MESSAGE
;
570 ct
->c_ctinitfnx
= InitMessage
;
573 * Else default type is text/plain
575 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
577 ct
->c_type
= CT_TEXT
;
578 ct
->c_ctinitfnx
= InitText
;
582 /* Use default Transfer-Encoding, if necessary */
584 ct
->c_encoding
= CE_7BIT
;
597 * small routine to add header field to list
601 add_header (CT ct
, char *name
, char *value
)
605 /* allocate header field structure */
608 /* link data into header structure */
613 /* link header structure into the list */
614 if (ct
->c_first_hf
== NULL
) {
615 ct
->c_first_hf
= hp
; /* this is the first */
618 ct
->c_last_hf
->next
= hp
; /* add it to the end */
627 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
628 * directives. Fills in the information of the CTinfo structure.
631 get_ctinfo (char *cp
, CT ct
, int magic
)
640 /* store copy of Content-Type line */
641 cp
= ct
->c_ctline
= mh_xstrdup(FENDNULL(cp
));
643 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
646 /* change newlines to spaces */
647 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
650 /* trim trailing spaces */
651 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
652 if (!isspace ((unsigned char) *dp
))
657 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
659 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
660 &ci
->ci_comment
) == NOTOK
)
663 for (dp
= cp
; istoken (*dp
); dp
++)
667 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
672 inform("invalid %s: field in message %s (empty type)",
673 TYPE_FIELD
, ct
->c_file
);
676 to_lower(ci
->ci_type
);
678 while (isspace ((unsigned char) *cp
))
681 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
682 &ci
->ci_comment
) == NOTOK
)
687 ci
->ci_subtype
= mh_xstrdup("");
692 while (isspace ((unsigned char) *cp
))
695 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
696 &ci
->ci_comment
) == NOTOK
)
699 for (dp
= cp
; istoken (*dp
); dp
++)
703 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
707 if (!*ci
->ci_subtype
) {
708 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
709 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
712 to_lower(ci
->ci_subtype
);
715 while (isspace ((unsigned char) *cp
))
718 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
719 &ci
->ci_comment
) == NOTOK
)
722 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
723 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
724 &ci
->ci_comment
)) != OK
) {
725 return status
== NOTOK
? NOTOK
: OK
;
729 * Get any <Content-Id> given in buffer
731 if (magic
&& *cp
== '<') {
734 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
735 inform("invalid ID in message %s", ct
->c_file
);
741 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
747 while (isspace ((unsigned char) *cp
))
752 * Get any [Content-Description] given in buffer.
754 if (magic
&& *cp
== '[') {
756 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
760 inform("invalid description in message %s", ct
->c_file
);
768 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
774 while (isspace ((unsigned char) *cp
))
779 * Get any {Content-Disposition} given in buffer.
781 if (magic
&& *cp
== '{') {
783 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
787 inform("invalid disposition in message %s", ct
->c_file
);
795 if (get_dispo(cp
, ct
, 1) != OK
)
801 while (isspace ((unsigned char) *cp
))
806 * Get any extension directives (right now just the content transfer
807 * encoding, but maybe others) that we care about.
810 if (magic
&& *cp
== '*') {
812 * See if it's a CTE we match on
817 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
821 inform("invalid null transfer encoding specification");
828 ct
->c_reqencoding
= CE_UNKNOWN
;
830 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
831 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
832 ct
->c_reqencoding
= kv
->kv_value
;
837 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
838 inform("invalid CTE specification: \"%s\"", dp
);
842 while (isspace ((unsigned char) *cp
))
847 * Check if anything is left over
851 ci
->ci_magic
= mh_xstrdup(cp
);
853 /* If there is a Content-Disposition header and it doesn't
854 have a *filename=, extract it from the magic contents.
855 The r1bindex call skips any leading directory
857 if (ct
->c_dispo_type
&&
858 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
859 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
860 r1bindex(ci
->ci_magic
, '/'), 0);
864 inform("extraneous information in message %s's %s: field\n"
865 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
873 * Parse out a Content-Disposition header. A lot of this is cribbed from
877 get_dispo (char *cp
, CT ct
, int buildflag
)
879 char *dp
, *dispoheader
;
884 * Save the whole copy of the Content-Disposition header, unless we're
885 * processing a mhbuild directive. A NULL c_dispo will be a flag to
886 * mhbuild that the disposition header needs to be generated at that
890 dispoheader
= cp
= mh_xstrdup(FENDNULL(cp
));
892 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
895 /* change newlines to spaces */
896 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
899 /* trim trailing spaces */
900 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
901 if (!isspace ((unsigned char) *dp
))
906 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
908 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
914 for (dp
= cp
; istoken (*dp
); dp
++)
918 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
922 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
925 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
926 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
928 if (status
== NOTOK
) {
933 inform("extraneous information in message %s's %s: field\n (%s)",
934 ct
->c_file
, DISPO_FIELD
, cp
);
940 ct
->c_dispo
= dispoheader
;
947 get_comment (const char *filename
, const char *fieldname
, char **ap
,
952 char c
, buffer
[BUFSIZ
], *dp
;
962 inform("invalid comment in message %s's %s: field",
963 filename
, fieldname
);
968 if ((c
= *cp
++) == '\0')
991 if ((dp
= *commentp
)) {
992 *commentp
= concat (dp
, " ", buffer
, NULL
);
995 *commentp
= mh_xstrdup(buffer
);
999 while (isspace ((unsigned char) *cp
))
1010 * Handles content types audio, image, and video.
1011 * There's not much to do right here.
1019 return OK
; /* not much to do here */
1030 char buffer
[BUFSIZ
];
1035 CI ci
= &ct
->c_ctinfo
;
1037 /* check for missing subtype */
1038 if (!*ci
->ci_subtype
)
1039 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1042 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1044 /* allocate text character set structure */
1046 ct
->c_ctparams
= (void *) t
;
1048 /* scan for charset parameter */
1049 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1050 if (!strcasecmp (pm
->pm_name
, "charset"))
1053 /* check if content specified a character set */
1055 chset
= pm
->pm_value
;
1056 t
->tx_charset
= CHARSET_SPECIFIED
;
1058 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1062 * If we can not handle character set natively,
1063 * then check profile for string to modify the
1064 * terminal or display method.
1066 * termproc is for mhshow, though mhlist -debug prints it, too.
1068 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1069 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1070 if ((cp
= context_find (buffer
)))
1071 ct
->c_termproc
= mh_xstrdup(cp
);
1083 InitMultiPart (CT ct
)
1093 struct multipart
*m
;
1094 struct part
*part
, **next
;
1095 CI ci
= &ct
->c_ctinfo
;
1100 * The encoding for multipart messages must be either
1101 * 7bit, 8bit, or binary (per RFC 2045).
1103 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1104 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1105 /* Copy the Content-Transfer-Encoding header field body so we can
1106 remove any trailing whitespace and leading blanks from it. */
1107 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1109 bp
= cte
+ strlen (cte
) - 1;
1110 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1111 for (bp
= cte
; isblank((unsigned char)*bp
); ++bp
) continue;
1113 inform("\"%s/%s\" type in message %s must be encoded in\n"
1114 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1115 "mhfixmsg -fixcte can fix it, or\n"
1116 "manually edit the file and change the \"%s\"\n"
1117 "Content-Transfer-Encoding to one of those. For now, continuing...",
1118 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1125 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1128 * Check for "boundary" parameter, which is
1129 * required for multipart messages.
1132 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1133 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1139 /* complain if boundary parameter is missing */
1141 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1142 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1146 /* allocate primary structure for multipart info */
1148 ct
->c_ctparams
= (void *) m
;
1150 /* check if boundary parameter contains only whitespace characters */
1151 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1154 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1155 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1159 /* remove trailing whitespace from boundary parameter */
1160 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1161 if (!isspace ((unsigned char) *dp
))
1165 /* record boundary separators */
1166 m
->mp_start
= concat (bp
, "\n", NULL
);
1167 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1169 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1170 advise (ct
->c_file
, "unable to open for reading");
1174 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1176 next
= &m
->mp_parts
;
1180 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1185 if (bufp
[0] != '-' || bufp
[1] != '-')
1188 if (strcmp (bufp
+ 2, m
->mp_start
))
1193 next
= &part
->mp_next
;
1195 if (!(p
= get_content (fp
, ct
->c_file
,
1196 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1204 fseek (fp
, pos
, SEEK_SET
);
1207 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1211 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1212 if (p
->c_end
< p
->c_begin
)
1213 p
->c_begin
= p
->c_end
;
1218 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1223 if (! suppress_bogus_mp_content_warning
) {
1224 inform("bogus multipart content in message %s", ct
->c_file
);
1226 bogus_mp_content
= true;
1228 if (!inout
&& part
) {
1230 p
->c_end
= ct
->c_end
;
1232 if (p
->c_begin
>= p
->c_end
) {
1233 for (next
= &m
->mp_parts
; *next
!= part
;
1234 next
= &((*next
)->mp_next
))
1243 /* reverse the order of the parts for multipart/alternative */
1244 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1250 * label all subparts with part number, and
1251 * then initialize the content of the subpart.
1256 char partnam
[BUFSIZ
];
1259 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1260 pp
= partnam
+ strlen (partnam
);
1265 for (part
= m
->mp_parts
, partnum
= 1; part
;
1266 part
= part
->mp_next
, partnum
++) {
1269 sprintf (pp
, "%d", partnum
);
1270 p
->c_partno
= mh_xstrdup(partnam
);
1272 /* initialize the content of the subparts */
1273 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1282 get_leftover_mp_content (ct
, 1);
1283 get_leftover_mp_content (ct
, 0);
1293 * reverse the order of the parts of a multipart/alternative,
1294 * presumably to put the "most favored" alternative first, for
1295 * ease of choosing/displaying it later on. from a mail message on
1296 * nmh-workers, from kenh:
1297 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1298 * see code in mhn that did the same thing... According to the RCS
1299 * logs, that code was around from the initial checkin of mhn.c by
1300 * John Romine in 1992, which is as far back as we have."
1303 reverse_parts (CT ct
)
1305 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1309 /* Reverse the order of its parts by walking the mp_parts list
1310 and pushing each node to the front. */
1311 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1312 next
= part
->mp_next
;
1313 part
->mp_next
= m
->mp_parts
;
1319 move_preferred_part(CT ct
, mime_type_subtype
*pref
)
1321 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1322 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1326 /* move the matching part(s) to the head of the list: walk the
1327 * list of parts, move matching parts to a new list (maintaining
1328 * their order), and finally, concatenate the old list onto the
1335 head
->mp_next
= m
->mp_parts
;
1336 nhead
->mp_next
= NULL
;
1340 part
= head
->mp_next
;
1341 while (part
!= NULL
) {
1342 ci
= &part
->mp_part
->c_ctinfo
;
1343 if (!strcasecmp(ci
->ci_type
, pref
->type
) &&
1345 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1346 prev
->mp_next
= part
->mp_next
;
1347 part
->mp_next
= NULL
;
1348 ntail
->mp_next
= part
;
1350 part
= prev
->mp_next
;
1353 part
= prev
->mp_next
;
1356 ntail
->mp_next
= head
->mp_next
;
1357 m
->mp_parts
= nhead
->mp_next
;
1361 * move parts that match the user's preferences (-prefer) to the head
1362 * of the line. process preferences in reverse so first one given
1363 * ends up first in line
1369 for (i
= 0; i
< npreferred
; i
++)
1370 move_preferred_part(ct
, mime_preference
+ i
);
1375 /* parse_mime() arranges alternates in reverse (priority) order. This
1376 function can be used to reverse them back. This will put, for
1377 example, a text/plain part before a text/html part in a
1378 multipart/alternative part, for example, where it belongs. */
1380 reverse_alternative_parts (CT ct
)
1382 if (ct
->c_type
== CT_MULTIPART
) {
1383 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1386 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1390 /* And call recursively on each part of a multipart. */
1391 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1392 reverse_alternative_parts (part
->mp_part
);
1405 CI ci
= &ct
->c_ctinfo
;
1407 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1408 inform("\"%s/%s\" type in message %s should be encoded in "
1409 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1414 /* check for missing subtype */
1415 if (!*ci
->ci_subtype
)
1416 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1419 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1421 switch (ct
->c_subtype
) {
1422 case MESSAGE_RFC822
:
1425 case MESSAGE_PARTIAL
:
1431 ct
->c_ctparams
= (void *) p
;
1433 /* scan for parameters "id", "number", and "total" */
1434 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1435 if (!strcasecmp (pm
->pm_name
, "id")) {
1436 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1439 if (!strcasecmp (pm
->pm_name
, "number")) {
1440 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1441 || p
->pm_partno
< 1) {
1443 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1444 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1445 ct
->c_file
, TYPE_FIELD
);
1450 if (!strcasecmp (pm
->pm_name
, "total")) {
1451 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1460 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1461 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1462 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1468 case MESSAGE_EXTERNAL
:
1476 ct
->c_ctparams
= (void *) e
;
1479 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1480 advise (ct
->c_file
, "unable to open for reading");
1484 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1486 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1494 p
->c_ceopenfnx
= NULL
;
1495 if ((exresult
= params_external (ct
, 0)) != NOTOK
1496 && p
->c_ceopenfnx
== openMail
) {
1500 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1502 content_error (NULL
, ct
,
1503 "empty body for access-type=mail-server");
1507 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1508 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1510 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1512 adios ("failed", "fread");
1515 die("unexpected EOF from fread");
1518 bp
+= cc
, size
-= cc
;
1525 p
->c_end
= p
->c_begin
;
1530 if (exresult
== NOTOK
)
1532 if (e
->eb_flags
== NOTOK
)
1535 switch (p
->c_type
) {
1540 if (p
->c_subtype
!= MESSAGE_RFC822
)
1544 e
->eb_partno
= ct
->c_partno
;
1546 (*p
->c_ctinitfnx
) (p
);
1561 params_external (CT ct
, int composing
)
1564 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1565 CI ci
= &ct
->c_ctinfo
;
1567 ct
->c_ceopenfnx
= NULL
;
1568 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1569 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1570 struct str2init
*s2i
;
1571 CT p
= e
->eb_content
;
1573 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1574 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1577 e
->eb_access
= pm
->pm_value
;
1578 e
->eb_flags
= NOTOK
;
1579 p
->c_encoding
= CE_EXTERNAL
;
1582 e
->eb_access
= s2i
->si_key
;
1583 e
->eb_flags
= s2i
->si_val
;
1584 p
->c_encoding
= CE_EXTERNAL
;
1586 /* Call the Init function for this external type */
1587 if ((*s2i
->si_init
)(p
) == NOTOK
)
1591 if (!strcasecmp (pm
->pm_name
, "name")) {
1592 e
->eb_name
= pm
->pm_value
;
1595 if (!strcasecmp (pm
->pm_name
, "permission")) {
1596 e
->eb_permission
= pm
->pm_value
;
1599 if (!strcasecmp (pm
->pm_name
, "site")) {
1600 e
->eb_site
= pm
->pm_value
;
1603 if (!strcasecmp (pm
->pm_name
, "directory")) {
1604 e
->eb_dir
= pm
->pm_value
;
1607 if (!strcasecmp (pm
->pm_name
, "mode")) {
1608 e
->eb_mode
= pm
->pm_value
;
1611 if (!strcasecmp (pm
->pm_name
, "size")) {
1612 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1615 if (!strcasecmp (pm
->pm_name
, "server")) {
1616 e
->eb_server
= pm
->pm_value
;
1619 if (!strcasecmp (pm
->pm_name
, "subject")) {
1620 e
->eb_subject
= pm
->pm_value
;
1623 if (!strcasecmp (pm
->pm_name
, "url")) {
1625 * According to RFC 2017, we have to remove all whitespace from
1629 char *u
, *p
= pm
->pm_value
;
1630 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1632 for (; *p
!= '\0'; p
++) {
1633 if (! isspace((unsigned char) *p
))
1640 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1641 e
->eb_body
= getcpy (pm
->pm_value
);
1646 if (!e
->eb_access
) {
1647 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1648 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1661 InitApplication (CT ct
)
1663 CI ci
= &ct
->c_ctinfo
;
1666 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1673 * TRANSFER ENCODINGS
1677 init_encoding (CT ct
, OpenCEFunc openfnx
)
1679 ct
->c_ceopenfnx
= openfnx
;
1680 ct
->c_ceclosefnx
= close_encoding
;
1681 ct
->c_cesizefnx
= size_encoding
;
1688 close_encoding (CT ct
)
1690 CE ce
= &ct
->c_cefile
;
1699 static unsigned long
1700 size_encoding (CT ct
)
1705 CE ce
= &ct
->c_cefile
;
1708 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1709 return (long) st
.st_size
;
1712 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1713 return (long) st
.st_size
;
1717 if (ct
->c_encoding
== CE_EXTERNAL
)
1718 return ct
->c_end
- ct
->c_begin
;
1721 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1722 return ct
->c_end
- ct
->c_begin
;
1724 if (fstat (fd
, &st
) != NOTOK
)
1725 size
= (long) st
.st_size
;
1729 (*ct
->c_ceclosefnx
) (ct
);
1741 return init_encoding (ct
, openBase64
);
1746 openBase64 (CT ct
, char **file
)
1750 bool own_ct_fp
= false;
1751 char *cp
, *buffer
= NULL
;
1752 /* sbeck -- handle suffixes */
1754 CE ce
= &ct
->c_cefile
;
1755 unsigned char *decoded
;
1757 unsigned char digest
[16];
1760 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1765 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1766 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1772 if (*file
== NULL
) {
1775 ce
->ce_file
= mh_xstrdup(*file
);
1779 /* sbeck@cise.ufl.edu -- handle suffixes */
1781 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1782 if (ce
->ce_unlink
) {
1783 /* Create temporary file with filename extension. */
1784 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1785 die("unable to create temporary file in %s",
1789 ce
->ce_file
= add (cp
, ce
->ce_file
);
1791 } else if (*file
== NULL
) {
1793 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1794 die("unable to create temporary file in %s",
1797 ce
->ce_file
= mh_xstrdup(tempfile
);
1800 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1801 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1805 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1806 die("internal error(1)");
1808 buffer
= mh_xmalloc (len
+ 1);
1811 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1812 content_error (ct
->c_file
, ct
, "unable to open for reading");
1818 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1821 switch (cc
= read (fd
, cp
, len
)) {
1823 content_error (ct
->c_file
, ct
, "error reading from");
1827 content_error (NULL
, ct
, "premature eof");
1838 /* decodeBase64() requires null-terminated input. */
1841 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1842 ct
->c_digested
? digest
: NULL
) != OK
)
1847 unsigned char *decoded_p
= decoded
;
1848 for (i
= 0; i
< decoded_len
; ++i
) {
1849 putc (*decoded_p
++, ce
->ce_fp
);
1852 if (ferror (ce
->ce_fp
)) {
1853 content_error (ce
->ce_file
, ct
, "error writing to");
1857 if (ct
->c_digested
) {
1858 if (memcmp(digest
, ct
->c_digest
,
1860 content_error (NULL
, ct
,
1861 "content integrity suspect (digest mismatch) -- continuing");
1864 fprintf (stderr
, "content integrity confirmed\n");
1870 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1872 if (fflush (ce
->ce_fp
)) {
1873 content_error (ce
->ce_file
, ct
, "error writing to");
1877 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1880 *file
= ce
->ce_file
;
1886 return fileno (ce
->ce_fp
);
1893 free_encoding (ct
, 0);
1903 static char hex2nib
[0x80] = {
1904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1909 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1910 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1911 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1912 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1913 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1914 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1915 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1916 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1917 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1918 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1919 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1926 return init_encoding (ct
, openQuoted
);
1931 openQuoted (CT ct
, char **file
)
1933 int cc
, digested
, len
, quoted
;
1934 bool own_ct_fp
= false;
1940 CE ce
= &ct
->c_cefile
;
1941 /* sbeck -- handle suffixes */
1946 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1951 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1952 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1958 if (*file
== NULL
) {
1961 ce
->ce_file
= mh_xstrdup(*file
);
1965 /* sbeck@cise.ufl.edu -- handle suffixes */
1967 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1968 if (ce
->ce_unlink
) {
1969 /* Create temporary file with filename extension. */
1970 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1971 die("unable to create temporary file in %s",
1975 ce
->ce_file
= add (cp
, ce
->ce_file
);
1977 } else if (*file
== NULL
) {
1979 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1980 die("unable to create temporary file in %s",
1983 ce
->ce_file
= mh_xstrdup(tempfile
);
1986 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1987 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1991 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1992 die("internal error(2)");
1995 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1996 content_error (ct
->c_file
, ct
, "unable to open for reading");
2002 if ((digested
= ct
->c_digested
))
2003 MD5Init (&mdContext
);
2007 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2009 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2010 content_error (NULL
, ct
, "premature eof");
2014 if ((cc
= gotlen
) > len
)
2018 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2019 if (!isspace ((unsigned char) *ep
))
2024 for (; cp
< ep
; cp
++) {
2026 /* in an escape sequence */
2028 /* at byte 1 of an escape sequence */
2029 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2030 /* next is byte 2 */
2033 /* at byte 2 of an escape sequence */
2035 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2036 putc (mask
, ce
->ce_fp
);
2038 MD5Update (&mdContext
, &mask
, 1);
2039 if (ferror (ce
->ce_fp
)) {
2040 content_error (ce
->ce_file
, ct
, "error writing to");
2043 /* finished escape sequence; next may be literal or a new
2044 * escape sequence */
2047 /* on to next byte */
2051 /* not in an escape sequence */
2053 /* starting an escape sequence, or invalid '='? */
2054 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2055 /* "=\n" soft line break, eat the \n */
2059 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2060 /* We don't have 2 bytes left, so this is an invalid
2061 * escape sequence; just show the raw bytes (below). */
2062 } else if (isxdigit ((unsigned char) cp
[1]) &&
2063 isxdigit ((unsigned char) cp
[2])) {
2064 /* Next 2 bytes are hex digits, making this a valid escape
2065 * sequence; let's decode it (above). */
2069 /* One or both of the next 2 is out of range, making this
2070 * an invalid escape sequence; just show the raw bytes
2074 /* Just show the raw byte. */
2075 putc (*cp
, ce
->ce_fp
);
2078 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2080 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2083 if (ferror (ce
->ce_fp
)) {
2084 content_error (ce
->ce_file
, ct
, "error writing to");
2090 content_error (NULL
, ct
,
2091 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2095 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2097 if (fflush (ce
->ce_fp
)) {
2098 content_error (ce
->ce_file
, ct
, "error writing to");
2103 unsigned char digest
[16];
2105 MD5Final (digest
, &mdContext
);
2106 if (memcmp(digest
, ct
->c_digest
,
2108 content_error (NULL
, ct
,
2109 "content integrity suspect (digest mismatch) -- continuing");
2111 fprintf (stderr
, "content integrity confirmed\n");
2114 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2117 *file
= ce
->ce_file
;
2123 return fileno (ce
->ce_fp
);
2126 free_encoding (ct
, 0);
2143 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2146 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2152 open7Bit (CT ct
, char **file
)
2155 bool own_ct_fp
= false;
2156 char buffer
[BUFSIZ
];
2157 /* sbeck -- handle suffixes */
2160 CE ce
= &ct
->c_cefile
;
2163 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2168 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2169 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2175 if (*file
== NULL
) {
2178 ce
->ce_file
= mh_xstrdup(*file
);
2182 /* sbeck@cise.ufl.edu -- handle suffixes */
2184 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2185 if (ce
->ce_unlink
) {
2186 /* Create temporary file with filename extension. */
2187 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2188 die("unable to create temporary file in %s",
2192 ce
->ce_file
= add (cp
, ce
->ce_file
);
2194 } else if (*file
== NULL
) {
2196 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2197 die("unable to create temporary file in %s",
2200 ce
->ce_file
= mh_xstrdup(tempfile
);
2203 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2204 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2208 if (ct
->c_type
== CT_MULTIPART
) {
2209 CI ci
= &ct
->c_ctinfo
;
2213 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2214 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2215 + 1 + strlen (ci
->ci_subtype
);
2216 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2219 fputs (buffer
, ce
->ce_fp
);
2223 if (ci
->ci_comment
) {
2224 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2225 fputs ("\n\t", ce
->ce_fp
);
2229 putc (' ', ce
->ce_fp
);
2232 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2235 fprintf (ce
->ce_fp
, "\n");
2237 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2239 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2241 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2242 fprintf (ce
->ce_fp
, "\n");
2245 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2246 die("internal error(3)");
2249 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2250 content_error (ct
->c_file
, ct
, "unable to open for reading");
2256 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2258 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2260 content_error (ct
->c_file
, ct
, "error reading from");
2264 content_error (NULL
, ct
, "premature eof");
2272 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2273 advise ("open7Bit", "fwrite");
2275 if (ferror (ce
->ce_fp
)) {
2276 content_error (ce
->ce_file
, ct
, "error writing to");
2281 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2283 if (fflush (ce
->ce_fp
)) {
2284 content_error (ce
->ce_file
, ct
, "error writing to");
2288 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2291 *file
= ce
->ce_file
;
2296 return fileno (ce
->ce_fp
);
2299 free_encoding (ct
, 0);
2313 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2315 char cachefile
[BUFSIZ
];
2318 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2323 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2324 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2330 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2331 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2332 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2333 ce
->ce_file
= mh_xstrdup(cachefile
);
2337 admonish (cachefile
, "unable to fopen for reading");
2340 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2344 *file
= ce
->ce_file
;
2345 *fd
= fileno (ce
->ce_fp
);
2356 return init_encoding (ct
, openFile
);
2361 openFile (CT ct
, char **file
)
2364 char cachefile
[BUFSIZ
];
2365 struct exbody
*e
= ct
->c_ctexbody
;
2366 CE ce
= &ct
->c_cefile
;
2368 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2380 content_error (NULL
, ct
, "missing name parameter");
2384 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2387 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2388 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2392 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2393 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2394 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2398 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2399 if ((fp
= fopen (cachefile
, "w"))) {
2401 char buffer
[BUFSIZ
];
2402 FILE *gp
= ce
->ce_fp
;
2404 fseek (gp
, 0L, SEEK_SET
);
2406 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2408 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2409 advise ("openFile", "fwrite");
2414 admonish (ce
->ce_file
, "error reading");
2415 (void) m_unlink (cachefile
);
2416 } else if (ferror (fp
)) {
2417 admonish (cachefile
, "error writing");
2418 (void) m_unlink (cachefile
);
2425 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2426 *file
= ce
->ce_file
;
2427 return fileno (ce
->ce_fp
);
2437 return init_encoding (ct
, openFTP
);
2442 openFTP (CT ct
, char **file
)
2448 char *bp
, *ftp
, *user
, *pass
;
2449 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2451 CE ce
= &ct
->c_cefile
;
2452 static char *username
= NULL
;
2453 static char *password
= NULL
;
2457 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2463 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2474 if (!e
->eb_name
|| !e
->eb_site
) {
2475 content_error (NULL
, ct
, "missing %s parameter",
2476 e
->eb_name
? "site": "name");
2480 /* Get the buffer ready to go */
2482 buflen
= sizeof(buffer
);
2485 * Construct the query message for user
2487 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2493 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2499 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2500 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2505 if (e
->eb_size
> 0) {
2506 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2511 snprintf (bp
, buflen
, "? ");
2514 * Now, check the answer
2516 if (!read_yes_or_no_if_tty (buffer
))
2521 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2525 ruserpass (e
->eb_site
, &username
, &password
, 0);
2530 ce
->ce_unlink
= (*file
== NULL
);
2532 cachefile
[0] = '\0';
2533 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2534 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2535 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2536 if (*file
== NULL
) {
2543 ce
->ce_file
= mh_xstrdup(*file
);
2545 ce
->ce_file
= mh_xstrdup(cachefile
);
2548 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2549 die("unable to create temporary file in %s",
2552 ce
->ce_file
= mh_xstrdup(tempfile
);
2555 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2556 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2565 vec
[vecp
++] = r1bindex (ftp
, '/');
2566 vec
[vecp
++] = e
->eb_site
;
2569 vec
[vecp
++] = e
->eb_dir
;
2570 vec
[vecp
++] = e
->eb_name
;
2571 vec
[vecp
++] = ce
->ce_file
,
2572 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2573 ? "ascii" : "binary";
2581 adios ("fork", "unable to");
2585 close (fileno (ce
->ce_fp
));
2587 fprintf (stderr
, "unable to exec ");
2593 if (pidXwait (child_id
, NULL
)) {
2594 username
= password
= NULL
;
2604 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2609 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2610 if ((fp
= fopen (cachefile
, "w"))) {
2612 FILE *gp
= ce
->ce_fp
;
2614 fseek (gp
, 0L, SEEK_SET
);
2616 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2618 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2619 advise ("openFTP", "fwrite");
2624 admonish (ce
->ce_file
, "error reading");
2625 (void) m_unlink (cachefile
);
2626 } else if (ferror (fp
)) {
2627 admonish (cachefile
, "error writing");
2628 (void) m_unlink (cachefile
);
2636 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2637 *file
= ce
->ce_file
;
2638 return fileno (ce
->ce_fp
);
2649 return init_encoding (ct
, openMail
);
2654 openMail (CT ct
, char **file
)
2656 int child_id
, fd
, vecp
;
2658 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2659 struct exbody
*e
= ct
->c_ctexbody
;
2660 CE ce
= &ct
->c_cefile
;
2662 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2673 if (!e
->eb_server
) {
2674 content_error (NULL
, ct
, "missing server parameter");
2678 /* Get buffer ready to go */
2680 buflen
= sizeof(buffer
);
2682 /* Now, construct query message */
2683 snprintf (bp
, buflen
, "Retrieve content");
2689 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2695 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2697 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2699 /* Now, check answer */
2700 if (!read_yes_or_no_if_tty (buffer
))
2704 vec
[vecp
++] = r1bindex (mailproc
, '/');
2705 vec
[vecp
++] = e
->eb_server
;
2706 vec
[vecp
++] = "-subject";
2707 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2708 vec
[vecp
++] = "-body";
2709 vec
[vecp
++] = e
->eb_body
;
2715 advise ("fork", "unable to");
2719 execvp (mailproc
, vec
);
2720 fprintf (stderr
, "unable to exec ");
2726 if (pidXwait (child_id
, NULL
) == OK
)
2727 inform("request sent");
2731 if (*file
== NULL
) {
2733 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2734 die("unable to create temporary file in %s",
2737 ce
->ce_file
= mh_xstrdup(tempfile
);
2740 ce
->ce_file
= mh_xstrdup(*file
);
2744 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2745 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2749 /* showproc is for mhshow and mhstore, though mhlist -debug
2750 * prints it, too. */
2751 free(ct
->c_showproc
);
2752 ct
->c_showproc
= mh_xstrdup("true");
2754 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2755 *file
= ce
->ce_file
;
2756 return fileno (ce
->ce_fp
);
2767 return init_encoding (ct
, openURL
);
2772 openURL (CT ct
, char **file
)
2774 struct exbody
*e
= ct
->c_ctexbody
;
2775 CE ce
= &ct
->c_cefile
;
2776 char *urlprog
, *program
;
2777 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2781 struct msgs_array args
= { 0, 0, NULL
};
2784 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2788 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2792 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2804 content_error(NULL
, ct
, "missing url parameter");
2808 ce
->ce_unlink
= (*file
== NULL
);
2810 cachefile
[0] = '\0';
2812 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2813 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2814 if (*file
== NULL
) {
2821 ce
->ce_file
= mh_xstrdup(*file
);
2823 ce
->ce_file
= mh_xstrdup(cachefile
);
2826 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2827 die("unable to create temporary file in %s",
2830 ce
->ce_file
= mh_xstrdup(tempfile
);
2833 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2834 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2838 switch (child_id
= fork()) {
2840 adios ("fork", "unable to");
2844 argsplit_msgarg(&args
, urlprog
, &program
);
2845 app_msgarg(&args
, e
->eb_url
);
2846 app_msgarg(&args
, NULL
);
2847 dup2(fileno(ce
->ce_fp
), 1);
2848 close(fileno(ce
->ce_fp
));
2849 execvp(program
, args
.msgs
);
2850 fprintf(stderr
, "Unable to exec ");
2856 if (pidXwait(child_id
, NULL
)) {
2864 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2869 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2870 if ((fp
= fopen(cachefile
, "w"))) {
2872 FILE *gp
= ce
->ce_fp
;
2874 fseeko(gp
, 0, SEEK_SET
);
2876 while ((cc
= fread(buffer
, sizeof(*buffer
),
2877 sizeof(buffer
), gp
)) > 0)
2878 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2879 advise ("openURL", "fwrite");
2885 admonish(ce
->ce_file
, "error reading");
2886 (void) m_unlink (cachefile
);
2893 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2894 *file
= ce
->ce_file
;
2895 return fileno(ce
->ce_fp
);
2900 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2901 * has to be base64 decoded.
2904 readDigest (CT ct
, char *cp
)
2906 unsigned char *digest
;
2909 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2910 const size_t maxlen
= sizeof ct
->c_digest
;
2912 if (strlen ((char *) digest
) <= maxlen
) {
2913 memcpy (ct
->c_digest
, digest
, maxlen
);
2918 fprintf (stderr
, "MD5 digest=");
2919 for (i
= 0; i
< maxlen
; ++i
) {
2920 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2922 fprintf (stderr
, "\n");
2928 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2929 (int) strlen ((char *) digest
));
2939 /* Multipart parts might have content before the first subpart and/or
2940 after the last subpart that hasn't been stored anywhere else, so do
2943 get_leftover_mp_content (CT ct
, int before
/* or after */)
2945 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2947 bool found_boundary
= false;
2953 char *content
= NULL
;
2955 if (! m
) return NOTOK
;
2958 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2960 /* Isolate the beginning of this part to the beginning of the
2961 first subpart and save any content between them. */
2962 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2963 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2964 boundary
= concat ("--", m
->mp_start
, NULL
);
2966 struct part
*last_subpart
= NULL
;
2967 struct part
*subpart
;
2969 /* Go to the last subpart to get its end position. */
2970 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2971 last_subpart
= subpart
;
2974 if (last_subpart
== NULL
) return NOTOK
;
2976 /* Isolate the end of the last subpart to the end of this part
2977 and save any content between them. */
2978 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2979 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2980 boundary
= concat ("--", m
->mp_stop
, NULL
);
2983 /* Back up by 1 to pick up the newline. */
2984 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2986 /* Don't look beyond beginning of first subpart (before) or
2987 next part (after). */
2988 if (read
> max
) bufp
[read
-max
] = '\0';
2991 if (! strcmp (bufp
, boundary
)) {
2992 found_boundary
= true;
2995 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2996 found_boundary
= true;
3001 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3003 char *old_content
= content
;
3004 content
= concat (content
, bufp
, NULL
);
3008 ? concat ("\n", bufp
, NULL
)
3009 : concat (bufp
, NULL
);
3014 if (found_boundary
|| read
> max
) break;
3016 if (read
> max
) break;
3020 /* Skip the newline if that's all there is. */
3024 /* Remove trailing newline, except at EOF. */
3025 if ((before
|| ! feof (ct
->c_fp
)) &&
3026 (cp
= content
+ strlen (content
)) > content
&&
3031 if (strlen (content
) > 1) {
3033 m
->mp_content_before
= content
;
3035 m
->mp_content_after
= content
;
3050 ct_type_str (int type
)
3053 case CT_APPLICATION
:
3054 return "application";
3070 return "unknown_type";
3076 ct_subtype_str (int type
, int subtype
)
3079 case CT_APPLICATION
:
3081 case APPLICATION_OCTETS
:
3083 case APPLICATION_POSTSCRIPT
:
3084 return "postscript";
3086 return "unknown_app_subtype";
3090 case MESSAGE_RFC822
:
3092 case MESSAGE_PARTIAL
:
3094 case MESSAGE_EXTERNAL
:
3097 return "unknown_msg_subtype";
3103 case MULTI_ALTERNATE
:
3104 return "alternative";
3107 case MULTI_PARALLEL
:
3112 return "unknown_multipart_subtype";
3123 return "unknown_text_subtype";
3126 return "unknown_type";
3132 ct_str_type (const char *type
)
3134 struct str2init
*s2i
;
3136 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3137 if (! strcasecmp (type
, s2i
->si_key
)) {
3141 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3150 ct_str_subtype (int type
, const char *subtype
)
3155 case CT_APPLICATION
:
3156 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3157 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3161 return kv
->kv_value
;
3163 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3164 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3168 return kv
->kv_value
;
3170 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3171 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3175 return kv
->kv_value
;
3177 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3178 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3182 return kv
->kv_value
;
3189 /* Find the content type and InitFunc for the CT. */
3190 const struct str2init
*
3191 get_ct_init (int type
)
3193 const struct str2init
*sp
;
3195 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3196 if (type
== sp
->si_val
) {
3205 ce_str (int encoding
)
3211 return "quoted-printable";
3227 /* Find the content type and InitFunc for the content encoding method. */
3228 const struct str2init
*
3229 get_ce_method (const char *method
)
3231 struct str2init
*sp
;
3233 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3234 if (! strcasecmp (method
, sp
->si_key
)) {
3243 * Parse a series of MIME attributes (or parameters) given a header as
3246 * Arguments include:
3248 * filename - Name of input file (for error messages)
3249 * fieldname - Name of field being processed
3250 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3251 * Updated to point to end of attributes when finished.
3252 * param_head - Pointer to head of parameter list
3253 * param_tail - Pointer to tail of parameter list
3254 * commentp - Pointer to header comment pointer (may be NULL)
3256 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3257 * DONE to indicate a benign error (minor parsing error, but the program
3262 parse_header_attrs (const char *filename
, const char *fieldname
,
3263 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3266 char *cp
= *header_attrp
;
3272 struct sectlist
*next
;
3278 struct sectlist
*sechead
;
3279 struct parmlist
*next
;
3280 } *pp
, *pp2
, *phead
= NULL
;
3282 while (*cp
== ';') {
3283 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3284 bool encoded
= false;
3285 bool partial
= false;
3286 int len
= 0, index
= 0;
3289 while (isspace ((unsigned char) *cp
))
3293 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3298 if (! suppress_extraneous_trailing_semicolon_warning
) {
3299 inform("extraneous trailing ';' in message %s's %s: "
3300 "parameter list", filename
, fieldname
);
3305 /* down case the attribute name */
3306 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3307 *dp
= tolower ((unsigned char) *dp
);
3309 for (up
= dp
; isspace ((unsigned char) *dp
);)
3311 if (dp
== cp
|| *dp
!= '=') {
3312 inform("invalid parameter in message %s's %s: field\n"
3313 " parameter %s (error detected at offset %ld)",
3314 filename
, fieldname
, cp
, (long)(dp
- cp
));
3319 * To handle RFC 2231, we have to deal with the following extensions:
3321 * name*=encoded-value
3322 * name*<N>=part-N-of-a-parameter-value
3323 * name*<N>*=encoded-part-N-of-a-parameter-value
3326 * If there's a * right before the equal sign, it's encoded.
3327 * If there's a * and one or more digits, then it's section N.
3329 * Remember we can have one or the other, or both. cp points to
3330 * beginning of name, up points past the last character in the
3334 for (vp
= cp
; vp
< up
; vp
++) {
3335 if (*vp
== '*' && vp
< up
- 1) {
3339 if (*vp
== '*' && vp
== up
- 1) {
3341 } else if (partial
) {
3342 if (isdigit((unsigned char) *vp
))
3343 index
= *vp
- '0' + index
* 10;
3345 inform("invalid parameter index in message %s's %s: field"
3346 "\n (parameter %s)", filename
, fieldname
, cp
);
3355 * Break out the parameter name and value sections and allocate
3359 nameptr
= mh_xmalloc(len
+ 1);
3360 strncpy(nameptr
, cp
, len
);
3361 nameptr
[len
] = '\0';
3363 for (dp
++; isspace ((unsigned char) *dp
);)
3368 * Single quotes delimit the character set and language tag.
3369 * They are required on the first section (or a complete
3374 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3380 charset
= mh_xmalloc(len
+ 1);
3381 strncpy(charset
, dp
, len
);
3382 charset
[len
] = '\0';
3388 inform("missing charset in message %s's %s: field\n"
3389 " (parameter %s)", filename
, fieldname
, nameptr
);
3395 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3402 lang
= mh_xmalloc(len
+ 1);
3403 strncpy(lang
, dp
, len
);
3410 inform("missing language tag in message %s's %s: field\n"
3411 " (parameter %s)", filename
, fieldname
, nameptr
);
3421 * At this point vp should be pointing at the beginning
3422 * of the encoded value/section. Continue until we reach
3423 * the end or get whitespace. But first, calculate the
3424 * length so we can allocate the correct buffer size.
3427 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3429 if (*(vp
+ 1) == '\0' ||
3430 !isxdigit((unsigned char) *(vp
+ 1)) ||
3431 *(vp
+ 2) == '\0' ||
3432 !isxdigit((unsigned char) *(vp
+ 2))) {
3433 inform("invalid encoded sequence in message %s's %s: field\n"
3434 " (parameter %s)", filename
, fieldname
, nameptr
);
3445 up
= valptr
= mh_xmalloc(len
+ 1);
3447 for (vp
= dp
; istoken(*vp
); vp
++) {
3449 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3460 * A "normal" string. If it's got a leading quote, then we
3461 * strip the quotes out. Otherwise go until we reach the end
3462 * or get whitespace. Note we scan it twice; once to get the
3463 * length, then the second time copies it into the destination
3470 for (cp
= dp
+ 1;;) {
3474 inform("invalid quoted-string in message %s's %s: field\n"
3475 " (parameter %s)", filename
, fieldname
, nameptr
);
3495 for (cp
= dp
; istoken (*cp
); cp
++) {
3500 valptr
= mh_xmalloc(len
+ 1);
3504 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3512 strncpy(valptr
, cp
= dp
, len
);
3520 * If 'partial' is set, we don't allocate a parameter now. We
3521 * put it on the parameter linked list to be reassembled later.
3523 * "phead" points to a list of all parameters we need to reassemble.
3524 * Each parameter has a list of sections. We insert the sections in
3529 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3530 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3545 * Insert this into the section linked list
3553 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3554 sp
->next
= pp
->sechead
;
3557 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3558 if (sp2
->index
== sp
->index
) {
3559 inform("duplicate index (%d) in message %s's %s: field"
3560 "\n (parameter %s)", sp
->index
, filename
,
3561 fieldname
, nameptr
);
3564 if (sp2
->index
< sp
->index
&&
3565 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3566 sp
->next
= sp2
->next
;
3573 inform("Internal error: cannot insert partial param "
3574 "in message %s's %s: field\n (parameter %s)",
3575 filename
, fieldname
, nameptr
);
3581 * Save our charset and lang tags.
3584 if (index
== 0 && encoded
) {
3586 pp
->charset
= charset
;
3591 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3592 pm
->pm_charset
= charset
;
3596 while (isspace ((unsigned char) *cp
))
3600 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3606 * Now that we're done, reassemble all of the partial parameters.
3609 for (pp
= phead
; pp
!= NULL
; ) {
3613 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3614 if (sp
->index
!= pindex
++) {
3615 inform("missing section %d for parameter in message "
3616 "%s's %s: field\n (parameter %s)", pindex
- 1,
3617 filename
, fieldname
, pp
->name
);
3623 p
= q
= mh_xmalloc(tlen
+ 1);
3624 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3625 memcpy(q
, sp
->value
, sp
->len
);
3635 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3636 pm
->pm_charset
= pp
->charset
;
3637 pm
->pm_lang
= pp
->lang
;
3648 * Return the charset for a particular content type.
3652 content_charset (CT ct
)
3654 char *ret_charset
= NULL
;
3656 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3658 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3663 * Create a string based on a list of output parameters. Assume that this
3664 * parameter string will be appended to an existing header, so start out
3665 * with the separator (;). Perform RFC 2231 encoding when necessary.
3669 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3671 char *paramout
= NULL
;
3672 char line
[CPERLIN
* 2], *q
;
3673 int curlen
, index
, cont
, encode
, i
;
3674 size_t valoff
, numchars
;
3676 while (params
!= NULL
) {
3682 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3685 if (strlen(params
->pm_name
) > CPERLIN
) {
3686 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3691 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3694 * Loop until we get a parameter that fits within a line. We
3695 * assume new lines start with a tab, so check our overflow based
3705 * At this point we're definitely continuing the line, so
3706 * be sure to include the parameter name and section index.
3709 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3710 params
->pm_name
, index
);
3713 * Both of these functions do a NUL termination
3717 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3718 numchars
, valoff
, index
);
3720 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3730 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3735 * "line" starts with a ;\n\t, so that doesn't count against
3736 * the length. But add 8 since it starts with a tab; that's
3737 * how we end up with 5.
3740 initialwidth
= strlen(line
) + 5;
3743 * At this point the line should be built, so add it to our
3744 * current output buffer.
3747 paramout
= add(line
, paramout
);
3751 * If this won't fit on the line, start a new one. Save room in
3752 * case we need a semicolon on the end
3755 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3767 * At this point, we're either finishing a continued parameter, or
3768 * we're working on a new one.
3772 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3773 params
->pm_name
, index
);
3775 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3780 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3781 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3783 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3784 strlen(params
->pm_value
+ valoff
), valoff
);
3791 paramout
= add(line
, paramout
);
3792 initialwidth
+= strlen(line
);
3794 params
= params
->pm_next
;
3798 *offsetout
= initialwidth
;
3804 * Calculate the size of a parameter.
3808 * pm - The parameter being output
3809 * index - If continuing the parameter, the index of the section
3811 * valueoff - The current offset into the parameter value that we're
3812 * working on (previous sections have consumed valueoff bytes).
3813 * encode - Set if we should perform encoding on this parameter section
3814 * (given that we're consuming bytesfit bytes).
3815 * cont - Set if the remaining data in value will not fit on a single
3816 * line and will need to be continued.
3817 * bytesfit - The number of bytes that we can consume from the parameter
3818 * value and still fit on a completely new line. The
3819 * calculation assumes the new line starts with a tab,
3820 * includes the parameter name and any encoding, and fits
3821 * within CPERLIN bytes. Will always be at least 1.
3825 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3828 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3829 size_t len
= 0, fit
= 0;
3830 int fitlimit
= 0, eightbit
, maxfit
;
3835 * Add up the length. First, start with the parameter name.
3838 len
= strlen(pm
->pm_name
);
3841 * Scan the parameter value and see if we need to do encoding for this
3845 eightbit
= contains8bit(start
, NULL
);
3848 * Determine if we need to encode this section. Encoding is necessary if:
3850 * - There are any 8-bit characters at all and we're on the first
3852 * - There are 8-bit characters within N bytes of our section start.
3853 * N is calculated based on the number of bytes it would take to
3854 * reach CPERLIN. Specifically:
3855 * 8 (starting tab) +
3856 * strlen(param name) +
3857 * 4 ('* for section marker, '=', opening/closing '"')
3859 * is the number of bytes used by everything that isn't part of the
3860 * value. So that gets subtracted from CPERLIN.
3863 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3864 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3865 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3869 len
++; /* Add in equal sign */
3873 * We're using maxfit as a marker for how many characters we can
3874 * fit into the line. Bump it by two because we're not using quotes
3881 * If we don't have a charset or language tag in this parameter,
3885 if (! pm
->pm_charset
) {
3886 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3887 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3888 die("8-bit characters in parameter \"%s\", but "
3889 "local character set is US-ASCII", pm
->pm_name
);
3892 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3894 len
++; /* For the encoding marker */
3897 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3902 * We know we definitely need to include an index. maxfit already
3903 * includes the section marker.
3905 len
+= strlen(indexchar
);
3907 for (p
= start
; *p
!= '\0'; p
++) {
3908 if (isparamencode(*p
)) {
3916 * Just so there's no confusion: maxfit is counting OUTPUT
3917 * characters (post-encoding). fit is counting INPUT characters.
3919 if (! fitlimit
&& maxfit
>= 0)
3921 else if (! fitlimit
)
3926 * Calculate the string length, but add room for quoting \
3927 * and " if necessary. Also account for quotes at beginning
3930 for (p
= start
; *p
!= '\0'; p
++) {
3941 if (! fitlimit
&& maxfit
>= 0)
3943 else if (! fitlimit
)
3960 * Output an encoded parameter string.
3964 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3965 size_t valueoff
, int index
)
3967 size_t outlen
= 0, n
;
3968 char *endptr
= output
+ len
, *p
;
3971 * First, output the marker for an encoded string.
3979 * If the index is 0, output the character set and language tag.
3980 * If theses were NULL, they should have already been filled in
3985 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3989 if (output
> endptr
) {
3990 inform("Internal error: parameter buffer overflow");
3996 * Copy over the value, encoding if necessary
3999 p
= pm
->pm_value
+ valueoff
;
4000 while (valuelen
-- > 0) {
4001 if (isparamencode(*p
)) {
4002 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4009 if (output
> endptr
) {
4010 inform("Internal error: parameter buffer overflow");
4021 * Output a "normal" parameter, without encoding. Be sure to escape
4022 * quotes and backslashes if necessary.
4026 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4030 char *endptr
= output
+ len
, *p
;
4036 p
= pm
->pm_value
+ valueoff
;
4038 while (valuelen
-- > 0) {
4049 if (output
> endptr
) {
4050 inform("Internal error: parameter buffer overflow");
4055 if (output
- 2 > endptr
) {
4056 inform("Internal error: parameter buffer overflow");
4067 * Add a parameter to the parameter linked list
4071 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4076 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4077 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4080 (*last
)->pm_next
= pm
;
4091 * Either replace a current parameter with a new value, or add the parameter
4092 * to the parameter linked list.
4096 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4100 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4101 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4103 * If nocopy is set, it's assumed that we own both name
4104 * and value. We don't need name, so we discard it now.
4109 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4114 return add_param(first
, last
, name
, value
, nocopy
);
4118 * Retrieve a parameter value from a parameter linked list. If the parameter
4119 * value needs converted to the local character set, do that now.
4123 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4125 while (first
!= NULL
) {
4126 if (strcasecmp(name
, first
->pm_name
) == 0) {
4128 return first
->pm_value
;
4129 return getcpy(get_param_value(first
, replace
));
4131 first
= first
->pm_next
;
4138 * Return a parameter value, converting to the local character set if
4143 get_param_value(PM pm
, char replace
)
4145 static char buffer
[4096]; /* I hope no parameters are larger */
4146 size_t bufsize
= sizeof(buffer
);
4151 ICONV_CONST
char *p
;
4152 #else /* HAVE_ICONV */
4154 #endif /* HAVE_ICONV */
4159 * If we don't have a character set indicated, it's assumed to be
4160 * US-ASCII. If it matches our character set, we don't need to convert
4164 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4165 strlen(pm
->pm_charset
))) {
4166 return pm
->pm_value
;
4170 * In this case, we need to convert. If we have iconv support, use
4171 * that. Otherwise, go through and simply replace every non-ASCII
4172 * character with the substitution character.
4177 bufsize
= sizeof(buffer
);
4178 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4180 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4181 if (cd
== (iconv_t
) -1) {
4185 inbytes
= strlen(pm
->pm_value
);
4189 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4190 if (errno
!= EILSEQ
) {
4195 * Reset shift state, substitute our character,
4196 * try to restart conversion.
4199 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4212 for (++p
, --inbytes
;
4213 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4232 #endif /* HAVE_ICONV */
4235 * Take everything non-ASCII and substitute the replacement character
4239 bufsize
= sizeof(buffer
);
4240 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4241 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))