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 adios (NULL
, "message format error in component #%d", compnum
);
367 adios (NULL
, "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
) {
1381 if (ct
->c_type
== CT_MULTIPART
) {
1382 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1385 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1389 /* And call recursively on each part of a multipart. */
1390 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1391 reverse_alternative_parts (part
->mp_part
);
1404 CI ci
= &ct
->c_ctinfo
;
1406 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1407 inform("\"%s/%s\" type in message %s should be encoded in "
1408 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1413 /* check for missing subtype */
1414 if (!*ci
->ci_subtype
)
1415 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1418 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1420 switch (ct
->c_subtype
) {
1421 case MESSAGE_RFC822
:
1424 case MESSAGE_PARTIAL
:
1430 ct
->c_ctparams
= (void *) p
;
1432 /* scan for parameters "id", "number", and "total" */
1433 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1434 if (!strcasecmp (pm
->pm_name
, "id")) {
1435 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1438 if (!strcasecmp (pm
->pm_name
, "number")) {
1439 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1440 || p
->pm_partno
< 1) {
1442 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1443 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1444 ct
->c_file
, TYPE_FIELD
);
1449 if (!strcasecmp (pm
->pm_name
, "total")) {
1450 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1459 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1460 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1461 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1467 case MESSAGE_EXTERNAL
:
1475 ct
->c_ctparams
= (void *) e
;
1478 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1479 advise (ct
->c_file
, "unable to open for reading");
1483 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1485 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1493 p
->c_ceopenfnx
= NULL
;
1494 if ((exresult
= params_external (ct
, 0)) != NOTOK
1495 && p
->c_ceopenfnx
== openMail
) {
1499 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1501 content_error (NULL
, ct
,
1502 "empty body for access-type=mail-server");
1506 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1507 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1509 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1511 adios ("failed", "fread");
1514 adios (NULL
, "unexpected EOF from fread");
1517 bp
+= cc
, size
-= cc
;
1524 p
->c_end
= p
->c_begin
;
1529 if (exresult
== NOTOK
)
1531 if (e
->eb_flags
== NOTOK
)
1534 switch (p
->c_type
) {
1539 if (p
->c_subtype
!= MESSAGE_RFC822
)
1543 e
->eb_partno
= ct
->c_partno
;
1545 (*p
->c_ctinitfnx
) (p
);
1560 params_external (CT ct
, int composing
)
1563 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1564 CI ci
= &ct
->c_ctinfo
;
1566 ct
->c_ceopenfnx
= NULL
;
1567 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1568 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1569 struct str2init
*s2i
;
1570 CT p
= e
->eb_content
;
1572 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1573 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1576 e
->eb_access
= pm
->pm_value
;
1577 e
->eb_flags
= NOTOK
;
1578 p
->c_encoding
= CE_EXTERNAL
;
1581 e
->eb_access
= s2i
->si_key
;
1582 e
->eb_flags
= s2i
->si_val
;
1583 p
->c_encoding
= CE_EXTERNAL
;
1585 /* Call the Init function for this external type */
1586 if ((*s2i
->si_init
)(p
) == NOTOK
)
1590 if (!strcasecmp (pm
->pm_name
, "name")) {
1591 e
->eb_name
= pm
->pm_value
;
1594 if (!strcasecmp (pm
->pm_name
, "permission")) {
1595 e
->eb_permission
= pm
->pm_value
;
1598 if (!strcasecmp (pm
->pm_name
, "site")) {
1599 e
->eb_site
= pm
->pm_value
;
1602 if (!strcasecmp (pm
->pm_name
, "directory")) {
1603 e
->eb_dir
= pm
->pm_value
;
1606 if (!strcasecmp (pm
->pm_name
, "mode")) {
1607 e
->eb_mode
= pm
->pm_value
;
1610 if (!strcasecmp (pm
->pm_name
, "size")) {
1611 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1614 if (!strcasecmp (pm
->pm_name
, "server")) {
1615 e
->eb_server
= pm
->pm_value
;
1618 if (!strcasecmp (pm
->pm_name
, "subject")) {
1619 e
->eb_subject
= pm
->pm_value
;
1622 if (!strcasecmp (pm
->pm_name
, "url")) {
1624 * According to RFC 2017, we have to remove all whitespace from
1628 char *u
, *p
= pm
->pm_value
;
1629 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1631 for (; *p
!= '\0'; p
++) {
1632 if (! isspace((unsigned char) *p
))
1639 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1640 e
->eb_body
= getcpy (pm
->pm_value
);
1645 if (!e
->eb_access
) {
1646 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1647 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1660 InitApplication (CT ct
)
1662 CI ci
= &ct
->c_ctinfo
;
1665 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1672 * TRANSFER ENCODINGS
1676 init_encoding (CT ct
, OpenCEFunc openfnx
)
1678 ct
->c_ceopenfnx
= openfnx
;
1679 ct
->c_ceclosefnx
= close_encoding
;
1680 ct
->c_cesizefnx
= size_encoding
;
1687 close_encoding (CT ct
)
1689 CE ce
= &ct
->c_cefile
;
1698 static unsigned long
1699 size_encoding (CT ct
)
1704 CE ce
= &ct
->c_cefile
;
1707 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1708 return (long) st
.st_size
;
1711 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1712 return (long) st
.st_size
;
1716 if (ct
->c_encoding
== CE_EXTERNAL
)
1717 return (ct
->c_end
- ct
->c_begin
);
1720 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1721 return (ct
->c_end
- ct
->c_begin
);
1723 if (fstat (fd
, &st
) != NOTOK
)
1724 size
= (long) st
.st_size
;
1728 (*ct
->c_ceclosefnx
) (ct
);
1740 return init_encoding (ct
, openBase64
);
1745 openBase64 (CT ct
, char **file
)
1748 int fd
, own_ct_fp
= 0;
1749 char *cp
, *buffer
= NULL
;
1750 /* sbeck -- handle suffixes */
1752 CE ce
= &ct
->c_cefile
;
1753 unsigned char *decoded
;
1755 unsigned char digest
[16];
1758 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1763 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1764 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1770 if (*file
== NULL
) {
1773 ce
->ce_file
= mh_xstrdup(*file
);
1777 /* sbeck@cise.ufl.edu -- handle suffixes */
1779 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1780 if (ce
->ce_unlink
) {
1781 /* Create temporary file with filename extension. */
1782 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1783 adios(NULL
, "unable to create temporary file in %s",
1787 ce
->ce_file
= add (cp
, ce
->ce_file
);
1789 } else if (*file
== NULL
) {
1791 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1792 adios(NULL
, "unable to create temporary file in %s",
1795 ce
->ce_file
= mh_xstrdup(tempfile
);
1798 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1799 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1803 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1804 adios (NULL
, "internal error(1)");
1806 buffer
= mh_xmalloc (len
+ 1);
1809 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1810 content_error (ct
->c_file
, ct
, "unable to open for reading");
1816 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1819 switch (cc
= read (fd
, cp
, len
)) {
1821 content_error (ct
->c_file
, ct
, "error reading from");
1825 content_error (NULL
, ct
, "premature eof");
1836 /* decodeBase64() requires null-terminated input. */
1839 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1840 ct
->c_digested
? digest
: NULL
) != OK
)
1845 unsigned char *decoded_p
= decoded
;
1846 for (i
= 0; i
< decoded_len
; ++i
) {
1847 putc (*decoded_p
++, ce
->ce_fp
);
1850 if (ferror (ce
->ce_fp
)) {
1851 content_error (ce
->ce_file
, ct
, "error writing to");
1855 if (ct
->c_digested
) {
1856 if (memcmp(digest
, ct
->c_digest
,
1858 content_error (NULL
, ct
,
1859 "content integrity suspect (digest mismatch) -- continuing");
1862 fprintf (stderr
, "content integrity confirmed\n");
1868 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1870 if (fflush (ce
->ce_fp
)) {
1871 content_error (ce
->ce_file
, ct
, "error writing to");
1875 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1878 *file
= ce
->ce_file
;
1884 return fileno (ce
->ce_fp
);
1891 free_encoding (ct
, 0);
1901 static char hex2nib
[0x80] = {
1902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
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, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1909 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1910 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1911 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1913 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1914 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1915 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1916 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1917 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1924 return init_encoding (ct
, openQuoted
);
1929 openQuoted (CT ct
, char **file
)
1931 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1937 CE ce
= &ct
->c_cefile
;
1938 /* sbeck -- handle suffixes */
1943 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1948 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1949 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1955 if (*file
== NULL
) {
1958 ce
->ce_file
= mh_xstrdup(*file
);
1962 /* sbeck@cise.ufl.edu -- handle suffixes */
1964 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1965 if (ce
->ce_unlink
) {
1966 /* Create temporary file with filename extension. */
1967 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1968 adios(NULL
, "unable to create temporary file in %s",
1972 ce
->ce_file
= add (cp
, ce
->ce_file
);
1974 } else if (*file
== NULL
) {
1976 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1977 adios(NULL
, "unable to create temporary file in %s",
1980 ce
->ce_file
= mh_xstrdup(tempfile
);
1983 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1984 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1988 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1989 adios (NULL
, "internal error(2)");
1992 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1993 content_error (ct
->c_file
, ct
, "unable to open for reading");
1999 if ((digested
= ct
->c_digested
))
2000 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((char *) digest
, (char *) 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
)
2154 int cc
, fd
, len
, own_ct_fp
= 0;
2155 char buffer
[BUFSIZ
];
2156 /* sbeck -- handle suffixes */
2159 CE ce
= &ct
->c_cefile
;
2162 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2167 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2168 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2174 if (*file
== NULL
) {
2177 ce
->ce_file
= mh_xstrdup(*file
);
2181 /* sbeck@cise.ufl.edu -- handle suffixes */
2183 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2184 if (ce
->ce_unlink
) {
2185 /* Create temporary file with filename extension. */
2186 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2187 adios(NULL
, "unable to create temporary file in %s",
2191 ce
->ce_file
= add (cp
, ce
->ce_file
);
2193 } else if (*file
== NULL
) {
2195 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2196 adios(NULL
, "unable to create temporary file in %s",
2199 ce
->ce_file
= mh_xstrdup(tempfile
);
2202 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2203 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2207 if (ct
->c_type
== CT_MULTIPART
) {
2208 CI ci
= &ct
->c_ctinfo
;
2212 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2213 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2214 + 1 + strlen (ci
->ci_subtype
);
2215 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2218 fputs (buffer
, ce
->ce_fp
);
2222 if (ci
->ci_comment
) {
2223 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2224 fputs ("\n\t", ce
->ce_fp
);
2228 putc (' ', ce
->ce_fp
);
2231 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2234 fprintf (ce
->ce_fp
, "\n");
2236 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2238 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2240 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2241 fprintf (ce
->ce_fp
, "\n");
2244 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2245 adios (NULL
, "internal error(3)");
2248 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2249 content_error (ct
->c_file
, ct
, "unable to open for reading");
2255 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2257 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2259 content_error (ct
->c_file
, ct
, "error reading from");
2263 content_error (NULL
, ct
, "premature eof");
2271 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2272 advise ("open7Bit", "fwrite");
2274 if (ferror (ce
->ce_fp
)) {
2275 content_error (ce
->ce_file
, ct
, "error writing to");
2280 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2282 if (fflush (ce
->ce_fp
)) {
2283 content_error (ce
->ce_file
, ct
, "error writing to");
2287 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2290 *file
= ce
->ce_file
;
2295 return fileno (ce
->ce_fp
);
2298 free_encoding (ct
, 0);
2312 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2314 char cachefile
[BUFSIZ
];
2317 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2322 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2323 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2329 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2330 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2331 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2332 ce
->ce_file
= mh_xstrdup(cachefile
);
2336 admonish (cachefile
, "unable to fopen for reading");
2339 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2343 *file
= ce
->ce_file
;
2344 *fd
= fileno (ce
->ce_fp
);
2355 return init_encoding (ct
, openFile
);
2360 openFile (CT ct
, char **file
)
2363 char cachefile
[BUFSIZ
];
2364 struct exbody
*e
= ct
->c_ctexbody
;
2365 CE ce
= &ct
->c_cefile
;
2367 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2379 content_error (NULL
, ct
, "missing name parameter");
2383 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2386 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2387 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2391 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2392 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2393 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2397 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2398 if ((fp
= fopen (cachefile
, "w"))) {
2400 char buffer
[BUFSIZ
];
2401 FILE *gp
= ce
->ce_fp
;
2403 fseek (gp
, 0L, SEEK_SET
);
2405 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2407 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2408 advise ("openFile", "fwrite");
2413 admonish (ce
->ce_file
, "error reading");
2414 (void) m_unlink (cachefile
);
2415 } else if (ferror (fp
)) {
2416 admonish (cachefile
, "error writing");
2417 (void) m_unlink (cachefile
);
2424 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2425 *file
= ce
->ce_file
;
2426 return fileno (ce
->ce_fp
);
2436 return init_encoding (ct
, openFTP
);
2441 openFTP (CT ct
, char **file
)
2443 int cachetype
, caching
, fd
;
2445 char *bp
, *ftp
, *user
, *pass
;
2446 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2448 CE ce
= &ct
->c_cefile
;
2449 static char *username
= NULL
;
2450 static char *password
= NULL
;
2454 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2460 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2471 if (!e
->eb_name
|| !e
->eb_site
) {
2472 content_error (NULL
, ct
, "missing %s parameter",
2473 e
->eb_name
? "site": "name");
2477 /* Get the buffer ready to go */
2479 buflen
= sizeof(buffer
);
2482 * Construct the query message for user
2484 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2490 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2496 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2497 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2502 if (e
->eb_size
> 0) {
2503 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2508 snprintf (bp
, buflen
, "? ");
2511 * Now, check the answer
2513 if (!read_yes_or_no_if_tty (buffer
))
2518 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2522 ruserpass (e
->eb_site
, &username
, &password
, 0);
2527 ce
->ce_unlink
= (*file
== NULL
);
2529 cachefile
[0] = '\0';
2530 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2531 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2532 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2533 if (*file
== NULL
) {
2540 ce
->ce_file
= mh_xstrdup(*file
);
2542 ce
->ce_file
= mh_xstrdup(cachefile
);
2545 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2546 adios(NULL
, "unable to create temporary file in %s",
2549 ce
->ce_file
= mh_xstrdup(tempfile
);
2552 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2553 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2562 vec
[vecp
++] = r1bindex (ftp
, '/');
2563 vec
[vecp
++] = e
->eb_site
;
2566 vec
[vecp
++] = e
->eb_dir
;
2567 vec
[vecp
++] = e
->eb_name
;
2568 vec
[vecp
++] = ce
->ce_file
,
2569 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2570 ? "ascii" : "binary";
2578 adios ("fork", "unable to");
2582 close (fileno (ce
->ce_fp
));
2584 fprintf (stderr
, "unable to exec ");
2590 if (pidXwait (child_id
, NULL
)) {
2591 username
= password
= NULL
;
2601 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2606 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2607 if ((fp
= fopen (cachefile
, "w"))) {
2609 FILE *gp
= ce
->ce_fp
;
2611 fseek (gp
, 0L, SEEK_SET
);
2613 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2615 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2616 advise ("openFTP", "fwrite");
2621 admonish (ce
->ce_file
, "error reading");
2622 (void) m_unlink (cachefile
);
2623 } else if (ferror (fp
)) {
2624 admonish (cachefile
, "error writing");
2625 (void) m_unlink (cachefile
);
2633 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2634 *file
= ce
->ce_file
;
2635 return fileno (ce
->ce_fp
);
2646 return init_encoding (ct
, openMail
);
2651 openMail (CT ct
, char **file
)
2653 int child_id
, fd
, vecp
;
2655 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2656 struct exbody
*e
= ct
->c_ctexbody
;
2657 CE ce
= &ct
->c_cefile
;
2659 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2670 if (!e
->eb_server
) {
2671 content_error (NULL
, ct
, "missing server parameter");
2675 /* Get buffer ready to go */
2677 buflen
= sizeof(buffer
);
2679 /* Now, construct query message */
2680 snprintf (bp
, buflen
, "Retrieve content");
2686 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2692 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2694 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2696 /* Now, check answer */
2697 if (!read_yes_or_no_if_tty (buffer
))
2701 vec
[vecp
++] = r1bindex (mailproc
, '/');
2702 vec
[vecp
++] = e
->eb_server
;
2703 vec
[vecp
++] = "-subject";
2704 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2705 vec
[vecp
++] = "-body";
2706 vec
[vecp
++] = e
->eb_body
;
2712 advise ("fork", "unable to");
2716 execvp (mailproc
, vec
);
2717 fprintf (stderr
, "unable to exec ");
2723 if (pidXwait (child_id
, NULL
) == OK
)
2724 inform("request sent");
2728 if (*file
== NULL
) {
2730 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2731 adios(NULL
, "unable to create temporary file in %s",
2734 ce
->ce_file
= mh_xstrdup(tempfile
);
2737 ce
->ce_file
= mh_xstrdup(*file
);
2741 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2742 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2746 /* showproc is for mhshow and mhstore, though mhlist -debug
2747 * prints it, too. */
2748 free(ct
->c_showproc
);
2749 ct
->c_showproc
= mh_xstrdup("true");
2751 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2752 *file
= ce
->ce_file
;
2753 return fileno (ce
->ce_fp
);
2764 return init_encoding (ct
, openURL
);
2769 openURL (CT ct
, char **file
)
2771 struct exbody
*e
= ct
->c_ctexbody
;
2772 CE ce
= &ct
->c_cefile
;
2773 char *urlprog
, *program
;
2774 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2775 int fd
, caching
, cachetype
;
2776 struct msgs_array args
= { 0, 0, NULL
};
2779 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2783 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2787 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2799 content_error(NULL
, ct
, "missing url parameter");
2803 ce
->ce_unlink
= (*file
== NULL
);
2805 cachefile
[0] = '\0';
2807 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2808 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2809 if (*file
== NULL
) {
2816 ce
->ce_file
= mh_xstrdup(*file
);
2818 ce
->ce_file
= mh_xstrdup(cachefile
);
2821 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2822 adios(NULL
, "unable to create temporary file in %s",
2825 ce
->ce_file
= mh_xstrdup(tempfile
);
2828 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2829 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2833 switch (child_id
= fork()) {
2835 adios ("fork", "unable to");
2839 argsplit_msgarg(&args
, urlprog
, &program
);
2840 app_msgarg(&args
, e
->eb_url
);
2841 app_msgarg(&args
, NULL
);
2842 dup2(fileno(ce
->ce_fp
), 1);
2843 close(fileno(ce
->ce_fp
));
2844 execvp(program
, args
.msgs
);
2845 fprintf(stderr
, "Unable to exec ");
2851 if (pidXwait(child_id
, NULL
)) {
2859 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2864 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2865 if ((fp
= fopen(cachefile
, "w"))) {
2867 FILE *gp
= ce
->ce_fp
;
2869 fseeko(gp
, 0, SEEK_SET
);
2871 while ((cc
= fread(buffer
, sizeof(*buffer
),
2872 sizeof(buffer
), gp
)) > 0)
2873 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2874 advise ("openURL", "fwrite");
2880 admonish(ce
->ce_file
, "error reading");
2881 (void) m_unlink (cachefile
);
2888 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2889 *file
= ce
->ce_file
;
2890 return fileno(ce
->ce_fp
);
2895 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2896 * has to be base64 decoded.
2899 readDigest (CT ct
, char *cp
)
2901 unsigned char *digest
;
2904 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2905 const size_t maxlen
= sizeof ct
->c_digest
;
2907 if (strlen ((char *) digest
) <= maxlen
) {
2908 memcpy (ct
->c_digest
, digest
, maxlen
);
2913 fprintf (stderr
, "MD5 digest=");
2914 for (i
= 0; i
< maxlen
; ++i
) {
2915 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2917 fprintf (stderr
, "\n");
2923 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2924 (int) strlen ((char *) digest
));
2934 /* Multipart parts might have content before the first subpart and/or
2935 after the last subpart that hasn't been stored anywhere else, so do
2938 get_leftover_mp_content (CT ct
, int before
/* or after */)
2940 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2942 int found_boundary
= 0;
2948 char *content
= NULL
;
2950 if (! m
) return NOTOK
;
2953 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2955 /* Isolate the beginning of this part to the beginning of the
2956 first subpart and save any content between them. */
2957 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2958 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2959 boundary
= concat ("--", m
->mp_start
, NULL
);
2961 struct part
*last_subpart
= NULL
;
2962 struct part
*subpart
;
2964 /* Go to the last subpart to get its end position. */
2965 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2966 last_subpart
= subpart
;
2969 if (last_subpart
== NULL
) return NOTOK
;
2971 /* Isolate the end of the last subpart to the end of this part
2972 and save any content between them. */
2973 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2974 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2975 boundary
= concat ("--", m
->mp_stop
, NULL
);
2978 /* Back up by 1 to pick up the newline. */
2979 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2981 /* Don't look beyond beginning of first subpart (before) or
2982 next part (after). */
2983 if (read
> max
) bufp
[read
-max
] = '\0';
2986 if (! strcmp (bufp
, boundary
)) {
2990 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2996 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2998 char *old_content
= content
;
2999 content
= concat (content
, bufp
, NULL
);
3003 ? concat ("\n", bufp
, NULL
)
3004 : concat (bufp
, NULL
);
3009 if (found_boundary
|| read
> max
) break;
3011 if (read
> max
) break;
3015 /* Skip the newline if that's all there is. */
3019 /* Remove trailing newline, except at EOF. */
3020 if ((before
|| ! feof (ct
->c_fp
)) &&
3021 (cp
= content
+ strlen (content
)) > content
&&
3026 if (strlen (content
) > 1) {
3028 m
->mp_content_before
= content
;
3030 m
->mp_content_after
= content
;
3045 ct_type_str (int type
) {
3047 case CT_APPLICATION
:
3048 return "application";
3064 return "unknown_type";
3070 ct_subtype_str (int type
, int subtype
) {
3072 case CT_APPLICATION
:
3074 case APPLICATION_OCTETS
:
3076 case APPLICATION_POSTSCRIPT
:
3077 return "postscript";
3079 return "unknown_app_subtype";
3083 case MESSAGE_RFC822
:
3085 case MESSAGE_PARTIAL
:
3087 case MESSAGE_EXTERNAL
:
3090 return "unknown_msg_subtype";
3096 case MULTI_ALTERNATE
:
3097 return "alternative";
3100 case MULTI_PARALLEL
:
3105 return "unknown_multipart_subtype";
3116 return "unknown_text_subtype";
3119 return "unknown_type";
3125 ct_str_type (const char *type
) {
3126 struct str2init
*s2i
;
3128 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3129 if (! strcasecmp (type
, s2i
->si_key
)) {
3133 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3142 ct_str_subtype (int type
, const char *subtype
) {
3146 case CT_APPLICATION
:
3147 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3148 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3152 return kv
->kv_value
;
3154 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3155 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3159 return kv
->kv_value
;
3161 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3162 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3166 return kv
->kv_value
;
3168 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3169 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3173 return kv
->kv_value
;
3180 /* Find the content type and InitFunc for the CT. */
3181 const struct str2init
*
3182 get_ct_init (int type
) {
3183 const struct str2init
*sp
;
3185 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3186 if (type
== sp
->si_val
) {
3195 ce_str (int encoding
) {
3200 return "quoted-printable";
3216 /* Find the content type and InitFunc for the content encoding method. */
3217 const struct str2init
*
3218 get_ce_method (const char *method
) {
3219 struct str2init
*sp
;
3221 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3222 if (! strcasecmp (method
, sp
->si_key
)) {
3231 * Parse a series of MIME attributes (or parameters) given a header as
3234 * Arguments include:
3236 * filename - Name of input file (for error messages)
3237 * fieldname - Name of field being processed
3238 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3239 * Updated to point to end of attributes when finished.
3240 * param_head - Pointer to head of parameter list
3241 * param_tail - Pointer to tail of parameter list
3242 * commentp - Pointer to header comment pointer (may be NULL)
3244 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3245 * DONE to indicate a benign error (minor parsing error, but the program
3250 parse_header_attrs (const char *filename
, const char *fieldname
,
3251 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3254 char *cp
= *header_attrp
;
3260 struct sectlist
*next
;
3266 struct sectlist
*sechead
;
3267 struct parmlist
*next
;
3268 } *pp
, *pp2
, *phead
= NULL
;
3270 while (*cp
== ';') {
3271 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3272 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3275 while (isspace ((unsigned char) *cp
))
3279 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3284 if (! suppress_extraneous_trailing_semicolon_warning
) {
3285 inform("extraneous trailing ';' in message %s's %s: "
3286 "parameter list", filename
, fieldname
);
3291 /* down case the attribute name */
3292 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3293 *dp
= tolower ((unsigned char) *dp
);
3295 for (up
= dp
; isspace ((unsigned char) *dp
);)
3297 if (dp
== cp
|| *dp
!= '=') {
3298 inform("invalid parameter in message %s's %s: field\n"
3299 " parameter %s (error detected at offset %ld)",
3300 filename
, fieldname
, cp
, (long)(dp
- cp
));
3305 * To handle RFC 2231, we have to deal with the following extensions:
3307 * name*=encoded-value
3308 * name*<N>=part-N-of-a-parameter-value
3309 * name*<N>*=encoded-part-N-of-a-parameter-value
3312 * If there's a * right before the equal sign, it's encoded.
3313 * If there's a * and one or more digits, then it's section N.
3315 * Remember we can have one or the other, or both. cp points to
3316 * beginning of name, up points past the last character in the
3320 for (vp
= cp
; vp
< up
; vp
++) {
3321 if (*vp
== '*' && vp
< up
- 1) {
3325 if (*vp
== '*' && vp
== up
- 1) {
3327 } else if (partial
) {
3328 if (isdigit((unsigned char) *vp
))
3329 index
= *vp
- '0' + index
* 10;
3331 inform("invalid parameter index in message %s's %s: field"
3332 "\n (parameter %s)", filename
, fieldname
, cp
);
3341 * Break out the parameter name and value sections and allocate
3345 nameptr
= mh_xmalloc(len
+ 1);
3346 strncpy(nameptr
, cp
, len
);
3347 nameptr
[len
] = '\0';
3349 for (dp
++; isspace ((unsigned char) *dp
);)
3354 * Single quotes delimit the character set and language tag.
3355 * They are required on the first section (or a complete
3360 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3366 charset
= mh_xmalloc(len
+ 1);
3367 strncpy(charset
, dp
, len
);
3368 charset
[len
] = '\0';
3374 inform("missing charset in message %s's %s: field\n"
3375 " (parameter %s)", filename
, fieldname
, nameptr
);
3381 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3388 lang
= mh_xmalloc(len
+ 1);
3389 strncpy(lang
, dp
, len
);
3396 inform("missing language tag in message %s's %s: field\n"
3397 " (parameter %s)", filename
, fieldname
, nameptr
);
3407 * At this point vp should be pointing at the beginning
3408 * of the encoded value/section. Continue until we reach
3409 * the end or get whitespace. But first, calculate the
3410 * length so we can allocate the correct buffer size.
3413 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3415 if (*(vp
+ 1) == '\0' ||
3416 !isxdigit((unsigned char) *(vp
+ 1)) ||
3417 *(vp
+ 2) == '\0' ||
3418 !isxdigit((unsigned char) *(vp
+ 2))) {
3419 inform("invalid encoded sequence in message %s's %s: field\n"
3420 " (parameter %s)", filename
, fieldname
, nameptr
);
3431 up
= valptr
= mh_xmalloc(len
+ 1);
3433 for (vp
= dp
; istoken(*vp
); vp
++) {
3435 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3446 * A "normal" string. If it's got a leading quote, then we
3447 * strip the quotes out. Otherwise go until we reach the end
3448 * or get whitespace. Note we scan it twice; once to get the
3449 * length, then the second time copies it into the destination
3456 for (cp
= dp
+ 1;;) {
3460 inform("invalid quoted-string in message %s's %s: field\n"
3461 " (parameter %s)", filename
, fieldname
, nameptr
);
3481 for (cp
= dp
; istoken (*cp
); cp
++) {
3486 valptr
= mh_xmalloc(len
+ 1);
3490 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3498 strncpy(valptr
, cp
= dp
, len
);
3506 * If 'partial' is set, we don't allocate a parameter now. We
3507 * put it on the parameter linked list to be reassembled later.
3509 * "phead" points to a list of all parameters we need to reassemble.
3510 * Each parameter has a list of sections. We insert the sections in
3515 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3516 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3531 * Insert this into the section linked list
3539 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3540 sp
->next
= pp
->sechead
;
3543 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3544 if (sp2
->index
== sp
->index
) {
3545 inform("duplicate index (%d) in message %s's %s: field"
3546 "\n (parameter %s)", sp
->index
, filename
,
3547 fieldname
, nameptr
);
3550 if (sp2
->index
< sp
->index
&&
3551 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3552 sp
->next
= sp2
->next
;
3559 inform("Internal error: cannot insert partial param "
3560 "in message %s's %s: field\n (parameter %s)",
3561 filename
, fieldname
, nameptr
);
3567 * Save our charset and lang tags.
3570 if (index
== 0 && encoded
) {
3572 pp
->charset
= charset
;
3577 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3578 pm
->pm_charset
= charset
;
3582 while (isspace ((unsigned char) *cp
))
3586 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3592 * Now that we're done, reassemble all of the partial parameters.
3595 for (pp
= phead
; pp
!= NULL
; ) {
3599 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3600 if (sp
->index
!= pindex
++) {
3601 inform("missing section %d for parameter in message "
3602 "%s's %s: field\n (parameter %s)", pindex
- 1,
3603 filename
, fieldname
, pp
->name
);
3609 p
= q
= mh_xmalloc(tlen
+ 1);
3610 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3611 memcpy(q
, sp
->value
, sp
->len
);
3621 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3622 pm
->pm_charset
= pp
->charset
;
3623 pm
->pm_lang
= pp
->lang
;
3634 * Return the charset for a particular content type.
3638 content_charset (CT ct
) {
3639 char *ret_charset
= NULL
;
3641 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3643 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3648 * Create a string based on a list of output parameters. Assume that this
3649 * parameter string will be appended to an existing header, so start out
3650 * with the separator (;). Perform RFC 2231 encoding when necessary.
3654 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3656 char *paramout
= NULL
;
3657 char line
[CPERLIN
* 2], *q
;
3658 int curlen
, index
, cont
, encode
, i
;
3659 size_t valoff
, numchars
;
3661 while (params
!= NULL
) {
3667 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3670 if (strlen(params
->pm_name
) > CPERLIN
) {
3671 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3676 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3679 * Loop until we get a parameter that fits within a line. We
3680 * assume new lines start with a tab, so check our overflow based
3690 * At this point we're definitely continuing the line, so
3691 * be sure to include the parameter name and section index.
3694 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3695 params
->pm_name
, index
);
3698 * Both of these functions do a NUL termination
3702 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3703 numchars
, valoff
, index
);
3705 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3715 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3720 * "line" starts with a ;\n\t, so that doesn't count against
3721 * the length. But add 8 since it starts with a tab; that's
3722 * how we end up with 5.
3725 initialwidth
= strlen(line
) + 5;
3728 * At this point the line should be built, so add it to our
3729 * current output buffer.
3732 paramout
= add(line
, paramout
);
3736 * If this won't fit on the line, start a new one. Save room in
3737 * case we need a semicolon on the end
3740 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3752 * At this point, we're either finishing a continued parameter, or
3753 * we're working on a new one.
3757 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3758 params
->pm_name
, index
);
3760 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3765 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3766 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3768 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3769 strlen(params
->pm_value
+ valoff
), valoff
);
3776 paramout
= add(line
, paramout
);
3777 initialwidth
+= strlen(line
);
3779 params
= params
->pm_next
;
3783 *offsetout
= initialwidth
;
3789 * Calculate the size of a parameter.
3793 * pm - The parameter being output
3794 * index - If continuing the parameter, the index of the section
3796 * valueoff - The current offset into the parameter value that we're
3797 * working on (previous sections have consumed valueoff bytes).
3798 * encode - Set if we should perform encoding on this parameter section
3799 * (given that we're consuming bytesfit bytes).
3800 * cont - Set if the remaining data in value will not fit on a single
3801 * line and will need to be continued.
3802 * bytesfit - The number of bytes that we can consume from the parameter
3803 * value and still fit on a completely new line. The
3804 * calculation assumes the new line starts with a tab,
3805 * includes the parameter name and any encoding, and fits
3806 * within CPERLIN bytes. Will always be at least 1.
3810 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3813 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3814 size_t len
= 0, fit
= 0;
3815 int fitlimit
= 0, eightbit
, maxfit
;
3820 * Add up the length. First, start with the parameter name.
3823 len
= strlen(pm
->pm_name
);
3826 * Scan the parameter value and see if we need to do encoding for this
3830 eightbit
= contains8bit(start
, NULL
);
3833 * Determine if we need to encode this section. Encoding is necessary if:
3835 * - There are any 8-bit characters at all and we're on the first
3837 * - There are 8-bit characters within N bytes of our section start.
3838 * N is calculated based on the number of bytes it would take to
3839 * reach CPERLIN. Specifically:
3840 * 8 (starting tab) +
3841 * strlen(param name) +
3842 * 4 ('* for section marker, '=', opening/closing '"')
3844 * is the number of bytes used by everything that isn't part of the
3845 * value. So that gets subtracted from CPERLIN.
3848 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3849 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3850 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3854 len
++; /* Add in equal sign */
3858 * We're using maxfit as a marker for how many characters we can
3859 * fit into the line. Bump it by two because we're not using quotes
3866 * If we don't have a charset or language tag in this parameter,
3870 if (! pm
->pm_charset
) {
3871 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3872 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3873 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3874 "local character set is US-ASCII", pm
->pm_name
);
3877 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3879 len
++; /* For the encoding marker */
3882 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3887 * We know we definitely need to include an index. maxfit already
3888 * includes the section marker.
3890 len
+= strlen(indexchar
);
3892 for (p
= start
; *p
!= '\0'; p
++) {
3893 if (isparamencode(*p
)) {
3901 * Just so there's no confusion: maxfit is counting OUTPUT
3902 * characters (post-encoding). fit is counting INPUT characters.
3904 if (! fitlimit
&& maxfit
>= 0)
3906 else if (! fitlimit
)
3911 * Calculate the string length, but add room for quoting \
3912 * and " if necessary. Also account for quotes at beginning
3915 for (p
= start
; *p
!= '\0'; p
++) {
3926 if (! fitlimit
&& maxfit
>= 0)
3928 else if (! fitlimit
)
3945 * Output an encoded parameter string.
3949 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3950 size_t valueoff
, int index
)
3952 size_t outlen
= 0, n
;
3953 char *endptr
= output
+ len
, *p
;
3956 * First, output the marker for an encoded string.
3964 * If the index is 0, output the character set and language tag.
3965 * If theses were NULL, they should have already been filled in
3970 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3974 if (output
> endptr
) {
3975 inform("Internal error: parameter buffer overflow");
3981 * Copy over the value, encoding if necessary
3984 p
= pm
->pm_value
+ valueoff
;
3985 while (valuelen
-- > 0) {
3986 if (isparamencode(*p
)) {
3987 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
3994 if (output
> endptr
) {
3995 inform("Internal error: parameter buffer overflow");
4006 * Output a "normal" parameter, without encoding. Be sure to escape
4007 * quotes and backslashes if necessary.
4011 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4015 char *endptr
= output
+ len
, *p
;
4021 p
= pm
->pm_value
+ valueoff
;
4023 while (valuelen
-- > 0) {
4034 if (output
> endptr
) {
4035 inform("Internal error: parameter buffer overflow");
4040 if (output
- 2 > endptr
) {
4041 inform("Internal error: parameter buffer overflow");
4052 * Add a parameter to the parameter linked list
4056 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4061 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4062 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4065 (*last
)->pm_next
= pm
;
4076 * Either replace a current parameter with a new value, or add the parameter
4077 * to the parameter linked list.
4081 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4085 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4086 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4088 * If nocopy is set, it's assumed that we own both name
4089 * and value. We don't need name, so we discard it now.
4094 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4099 return add_param(first
, last
, name
, value
, nocopy
);
4103 * Retrieve a parameter value from a parameter linked list. If the parameter
4104 * value needs converted to the local character set, do that now.
4108 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4110 while (first
!= NULL
) {
4111 if (strcasecmp(name
, first
->pm_name
) == 0) {
4113 return first
->pm_value
;
4114 return getcpy(get_param_value(first
, replace
));
4116 first
= first
->pm_next
;
4123 * Return a parameter value, converting to the local character set if
4127 char *get_param_value(PM pm
, char replace
)
4129 static char buffer
[4096]; /* I hope no parameters are larger */
4130 size_t bufsize
= sizeof(buffer
);
4135 ICONV_CONST
char *p
;
4136 #else /* HAVE_ICONV */
4138 #endif /* HAVE_ICONV */
4143 * If we don't have a character set indicated, it's assumed to be
4144 * US-ASCII. If it matches our character set, we don't need to convert
4148 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4149 strlen(pm
->pm_charset
))) {
4150 return pm
->pm_value
;
4154 * In this case, we need to convert. If we have iconv support, use
4155 * that. Otherwise, go through and simply replace every non-ASCII
4156 * character with the substitution character.
4161 bufsize
= sizeof(buffer
);
4162 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4164 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4165 if (cd
== (iconv_t
) -1) {
4169 inbytes
= strlen(pm
->pm_value
);
4173 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4174 if (errno
!= EILSEQ
) {
4179 * Reset shift state, substitute our character,
4180 * try to restart conversion.
4183 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4196 for (++p
, --inbytes
;
4197 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4216 #endif /* HAVE_ICONV */
4219 * Take everything non-ASCII and substitute the replacement character
4223 bufsize
= sizeof(buffer
);
4224 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4225 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))