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.
10 #include "sbr/check_charset.h"
11 #include "sbr/getcpy.h"
12 #include "sbr/context_find.h"
13 #include "sbr/pidstatus.h"
14 #include "sbr/arglist.h"
15 #include "sbr/error.h"
21 #include "h/mhparse.h"
24 #include "h/mhcachesbr.h"
25 #include "sbr/m_mktemp.h"
29 #endif /* HAVE_ICONV */
30 #include "sbr/base64.h"
35 int checksw
= 0; /* check Content-MD5 field */
38 * These are for mhfixmsg to:
39 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
41 * 2) Suppress the warning about bogus multipart content, and report it.
42 * 3) Suppress the warning about extraneous trailing ';' in header parameter
45 bool skip_mp_cte_check
;
46 bool suppress_bogus_mp_content_warning
;
47 bool bogus_mp_content
;
48 bool suppress_extraneous_trailing_semicolon_warning
;
51 * By default, suppress warning about multiple MIME-Version header fields.
53 bool suppress_multiple_mime_version_warning
= true;
55 /* list of preferred type/subtype pairs, for -prefer */
56 mime_type_subtype mime_preference
[NPREFS
];
61 * Structures for TEXT messages
63 struct k2v SubText
[] = {
64 { "plain", TEXT_PLAIN
},
65 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
66 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
67 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
70 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
73 * Structures for MULTIPART messages
75 struct k2v SubMultiPart
[] = {
76 { "mixed", MULTI_MIXED
},
77 { "alternative", MULTI_ALTERNATE
},
78 { "digest", MULTI_DIGEST
},
79 { "parallel", MULTI_PARALLEL
},
80 { "related", MULTI_RELATED
},
81 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
85 * Structures for MESSAGE messages
87 struct k2v SubMessage
[] = {
88 { "rfc822", MESSAGE_RFC822
},
89 { "partial", MESSAGE_PARTIAL
},
90 { "external-body", MESSAGE_EXTERNAL
},
91 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
95 * Structure for APPLICATION messages
97 struct k2v SubApplication
[] = {
98 { "octet-stream", APPLICATION_OCTETS
},
99 { "postscript", APPLICATION_POSTSCRIPT
},
100 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
104 * Mapping of names of CTE types in mhbuild directives
106 static struct k2v EncodingType
[] = {
109 { "q-p", CE_QUOTED
},
110 { "quoted-printable", CE_QUOTED
},
111 { "b64", CE_BASE64
},
112 { "base64", CE_BASE64
},
120 static CT
get_content (FILE *, char *, int);
121 static int get_comment (const char *, const char *, char **, char **);
123 static int InitGeneric (CT
);
124 static int InitText (CT
);
125 static int InitMultiPart (CT
);
126 static void reverse_parts (CT
);
127 static void prefer_parts(CT ct
);
128 static int InitMessage (CT
);
129 static int InitApplication (CT
);
130 static int init_encoding (CT
, OpenCEFunc
);
131 static unsigned long size_encoding (CT
);
132 static int InitBase64 (CT
);
133 static int openBase64 (CT
, char **);
134 static int InitQuoted (CT
);
135 static int openQuoted (CT
, char **);
136 static int Init7Bit (CT
);
137 static int openExternal (CT
, CT
, CE
, char **, int *);
138 static int InitFile (CT
);
139 static int openFile (CT
, char **);
140 static int InitFTP (CT
);
141 static int openFTP (CT
, char **);
142 static int InitMail (CT
);
143 static int openMail (CT
, char **);
144 static int readDigest (CT
, char *);
145 static int get_leftover_mp_content (CT
, int);
146 static int InitURL (CT
);
147 static int openURL (CT
, char **);
148 static int parse_header_attrs (const char *, const char *, char **, PM
*,
150 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
151 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
152 static int get_dispo (char *, CT
, int);
154 struct str2init str2cts
[] = {
155 { "application", CT_APPLICATION
, InitApplication
},
156 { "audio", CT_AUDIO
, InitGeneric
},
157 { "image", CT_IMAGE
, InitGeneric
},
158 { "message", CT_MESSAGE
, InitMessage
},
159 { "multipart", CT_MULTIPART
, InitMultiPart
},
160 { "text", CT_TEXT
, InitText
},
161 { "video", CT_VIDEO
, InitGeneric
},
162 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
163 { NULL
, CT_UNKNOWN
, NULL
},
166 struct str2init str2ces
[] = {
167 { "base64", CE_BASE64
, InitBase64
},
168 { "quoted-printable", CE_QUOTED
, InitQuoted
},
169 { "8bit", CE_8BIT
, Init7Bit
},
170 { "7bit", CE_7BIT
, Init7Bit
},
171 { "binary", CE_BINARY
, Init7Bit
},
172 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
173 { NULL
, CE_UNKNOWN
, NULL
},
177 * NOTE WELL: si_key MUST NOT have value of NOTOK
179 * si_val is 1 if access method is anonymous.
181 struct str2init str2methods
[] = {
182 { "afs", 1, InitFile
},
183 { "anon-ftp", 1, InitFTP
},
184 { "ftp", 0, InitFTP
},
185 { "local-file", 0, InitFile
},
186 { "mail-server", 0, InitMail
},
187 { "url", 0, InitURL
},
193 * Main entry point for parsing a MIME message or file.
194 * It returns the Content structure for the top level
195 * entity in the file.
199 parse_mime (char *file
)
208 bogus_mp_content
= false;
211 * Check if file is actually standard input
213 if ((is_stdin
= !(strcmp (file
, "-")))) {
214 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
216 advise("mhparse", "unable to create temporary file in %s",
220 file
= mh_xstrdup(tfile
);
222 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
223 if (fwrite(buffer
, 1, n
, fp
) != n
) {
224 (void) m_unlink (file
);
225 advise (file
, "error copying to temporary file");
231 if (ferror (stdin
)) {
232 (void) m_unlink (file
);
233 advise ("stdin", "error reading");
237 (void) m_unlink (file
);
238 advise (file
, "error writing");
241 fseek (fp
, 0L, SEEK_SET
);
242 } else if (stat (file
, &statbuf
) == NOTOK
) {
243 advise (file
, "unable to stat");
245 } else if (S_ISDIR(statbuf
.st_mode
)) {
246 /* Don't try to parse a directory. */
247 inform("%s is a directory", file
);
249 } else if ((fp
= fopen (file
, "r")) == NULL
) {
250 advise (file
, "unable to read");
254 if (!(ct
= get_content (fp
, file
, 1))) {
256 (void) m_unlink (file
);
257 inform("unable to decode %s", file
);
262 ct
->c_unlink
= 1; /* temp file to remove */
266 if (ct
->c_end
== 0L) {
267 fseek (fp
, 0L, SEEK_END
);
268 ct
->c_end
= ftell (fp
);
271 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
283 * Main routine for reading/parsing the headers
284 * of a message content.
286 * toplevel = 1 # we are at the top level of the message
287 * toplevel = 0 # we are inside message type or multipart type
288 * # other than multipart/digest
289 * toplevel = -1 # we are inside multipart/digest
290 * NB: on failure we will fclose(in)!
294 get_content (FILE *in
, char *file
, int toplevel
)
297 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
301 m_getfld_state_t gstate
;
303 /* allocate the content structure */
306 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
307 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
310 * Parse the header fields for this
311 * content into a linked list.
313 gstate
= m_getfld_state_init(in
);
314 m_getfld_track_filepos2(&gstate
);
315 for (compnum
= 1;;) {
316 int bufsz
= sizeof buf
;
317 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
322 /* get copies of the buffers */
323 np
= mh_xstrdup(name
);
324 vp
= mh_xstrdup(buf
);
326 /* if necessary, get rest of field */
327 while (state
== FLDPLUS
) {
329 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
330 vp
= add (buf
, vp
); /* add to previous value */
333 /* Now add the header data to the list */
334 add_header (ct
, np
, vp
);
336 /* continue, to see if this isn't the last header field */
337 ct
->c_begin
= ftell (in
) + 1;
341 /* There are two cases. The unusual one is when there is no
342 * blank line between the headers and the body. This is
343 * indicated by the name of the header starting with `:'.
345 * For both cases, normal first, `1' is the desired c_begin
346 * file position for the start of the body, and `2' is the
347 * file position when buf is returned.
349 * f o o : b a r \n \n b o d y \n bufsz = 6
351 * f o o : b a r \n b o d y \n bufsz = 4
354 * For the normal case, bufsz includes the
355 * header-terminating `\n', even though it is not in buf,
356 * but bufsz isn't affected when it's missing in the unusual
358 if (name
[0] == ':') {
359 ct
->c_begin
= ftell(in
) - bufsz
;
361 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
366 ct
->c_begin
= ftell (in
);
371 die("message format error in component #%d", compnum
);
374 die("getfld() returned %d", state
);
377 /* break out of the loop */
380 m_getfld_state_destroy (&gstate
);
383 * Read the content headers. We will parse the
384 * MIME related header fields into their various
385 * structures and set internal flags related to
386 * content type/subtype, etc.
389 hp
= ct
->c_first_hf
; /* start at first header field */
391 /* Get MIME-Version field */
392 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
397 vrsn
= mh_xstrdup(FENDNULL(hp
->value
));
399 /* Now, cleanup this field */
402 while (isspace ((unsigned char) *cp
))
404 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
406 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
407 if (!isspace ((unsigned char) *dp
))
411 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
414 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
417 for (dp
= cp
; istoken (*dp
); dp
++)
421 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
424 inform("message %s has unknown value for %s: field (%s), continuing...",
425 ct
->c_file
, VRSN_FIELD
, cp
);
430 if (! suppress_multiple_mime_version_warning
)
431 inform("message %s has multiple %s: fields",
432 ct
->c_file
, VRSN_FIELD
);
436 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
437 /* Get Content-Type field */
438 struct str2init
*s2i
;
439 CI ci
= &ct
->c_ctinfo
;
441 /* Check if we've already seen a Content-Type header */
443 inform("message %s has multiple %s: fields",
444 ct
->c_file
, TYPE_FIELD
);
448 /* Parse the Content-Type field */
449 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
453 * Set the Init function and the internal
454 * flag for this content type.
456 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
457 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
459 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
461 ct
->c_type
= s2i
->si_val
;
462 ct
->c_ctinitfnx
= s2i
->si_init
;
464 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
465 /* Get Content-Transfer-Encoding field */
467 struct str2init
*s2i
;
470 * Check if we've already seen the
471 * Content-Transfer-Encoding field
474 inform("message %s has multiple %s: fields",
475 ct
->c_file
, ENCODING_FIELD
);
479 /* get copy of this field */
480 ct
->c_celine
= cp
= mh_xstrdup(FENDNULL(hp
->value
));
482 while (isspace ((unsigned char) *cp
))
484 for (dp
= cp
; istoken (*dp
); dp
++)
490 * Find the internal flag and Init function
491 * for this transfer encoding.
493 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
494 if (!strcasecmp (cp
, s2i
->si_key
))
496 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
499 ct
->c_encoding
= s2i
->si_val
;
501 /* Call the Init function for this encoding */
502 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
505 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
506 /* Get Content-MD5 field */
512 if (ct
->c_digested
) {
513 inform("message %s has multiple %s: fields",
514 ct
->c_file
, MD5_FIELD
);
518 ep
= cp
= mh_xstrdup(FENDNULL(hp
->value
)); /* get a copy */
520 while (isspace ((unsigned char) *cp
))
522 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
524 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
525 if (!isspace ((unsigned char) *dp
))
529 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
532 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
537 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
545 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
546 /* Get Content-ID field */
547 ct
->c_id
= add (hp
->value
, ct
->c_id
);
549 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
550 /* Get Content-Description field */
551 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
553 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
554 /* Get Content-Disposition field */
555 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
560 hp
= hp
->next
; /* next header field */
564 * Check if we saw a Content-Type field.
565 * If not, then assign a default value for
566 * it, and the Init function.
570 * If we are inside a multipart/digest message,
571 * so default type is message/rfc822
574 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
576 ct
->c_type
= CT_MESSAGE
;
577 ct
->c_ctinitfnx
= InitMessage
;
580 * Else default type is text/plain
582 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
584 ct
->c_type
= CT_TEXT
;
585 ct
->c_ctinitfnx
= InitText
;
589 /* Use default Transfer-Encoding, if necessary */
591 ct
->c_encoding
= CE_7BIT
;
604 * small routine to add header field to list
608 add_header (CT ct
, char *name
, char *value
)
612 /* allocate header field structure */
615 /* link data into header structure */
620 /* link header structure into the list */
621 if (ct
->c_first_hf
== NULL
) {
622 ct
->c_first_hf
= hp
; /* this is the first */
625 ct
->c_last_hf
->next
= hp
; /* add it to the end */
634 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
635 * directives. Fills in the information of the CTinfo structure.
638 get_ctinfo (char *cp
, CT ct
, int magic
)
647 /* store copy of Content-Type line */
648 cp
= ct
->c_ctline
= mh_xstrdup(FENDNULL(cp
));
650 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
653 /* change newlines to spaces */
654 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
657 /* trim trailing spaces */
658 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
659 if (!isspace ((unsigned char) *dp
))
664 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
666 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
667 &ci
->ci_comment
) == NOTOK
)
670 for (dp
= cp
; istoken (*dp
); dp
++)
674 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
679 inform("invalid %s: field in message %s (empty type)",
680 TYPE_FIELD
, ct
->c_file
);
683 to_lower(ci
->ci_type
);
685 while (isspace ((unsigned char) *cp
))
688 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
689 &ci
->ci_comment
) == NOTOK
)
694 ci
->ci_subtype
= mh_xstrdup("");
699 while (isspace ((unsigned char) *cp
))
702 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
703 &ci
->ci_comment
) == NOTOK
)
706 for (dp
= cp
; istoken (*dp
); dp
++)
710 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
714 if (!*ci
->ci_subtype
) {
715 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
716 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
719 to_lower(ci
->ci_subtype
);
722 while (isspace ((unsigned char) *cp
))
725 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
726 &ci
->ci_comment
) == NOTOK
)
729 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
730 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
731 &ci
->ci_comment
)) != OK
) {
732 return status
== NOTOK
? NOTOK
: OK
;
736 * Get any <Content-Id> given in buffer
738 if (magic
&& *cp
== '<') {
741 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
742 inform("invalid ID in message %s", ct
->c_file
);
748 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
754 while (isspace ((unsigned char) *cp
))
759 * Get any [Content-Description] given in buffer.
761 if (magic
&& *cp
== '[') {
763 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
767 inform("invalid description in message %s", ct
->c_file
);
775 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
781 while (isspace ((unsigned char) *cp
))
786 * Get any {Content-Disposition} given in buffer.
788 if (magic
&& *cp
== '{') {
790 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
794 inform("invalid disposition in message %s", ct
->c_file
);
802 if (get_dispo(cp
, ct
, 1) != OK
)
808 while (isspace ((unsigned char) *cp
))
813 * Get any extension directives (right now just the content transfer
814 * encoding, but maybe others) that we care about.
817 if (magic
&& *cp
== '*') {
819 * See if it's a CTE we match on
824 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
828 inform("invalid null transfer encoding specification");
835 ct
->c_reqencoding
= CE_UNKNOWN
;
837 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
838 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
839 ct
->c_reqencoding
= kv
->kv_value
;
844 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
845 inform("invalid CTE specification: \"%s\"", dp
);
849 while (isspace ((unsigned char) *cp
))
854 * Check if anything is left over
858 ci
->ci_magic
= mh_xstrdup(cp
);
860 /* If there is a Content-Disposition header and it doesn't
861 have a *filename=, extract it from the magic contents.
862 The r1bindex call skips any leading directory
864 if (ct
->c_dispo_type
&&
865 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
866 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
867 r1bindex(ci
->ci_magic
, '/'), 0);
871 inform("extraneous information in message %s's %s: field\n"
872 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
880 * Parse out a Content-Disposition header. A lot of this is cribbed from
884 get_dispo (char *cp
, CT ct
, int buildflag
)
886 char *dp
, *dispoheader
;
891 * Save the whole copy of the Content-Disposition header, unless we're
892 * processing a mhbuild directive. A NULL c_dispo will be a flag to
893 * mhbuild that the disposition header needs to be generated at that
897 dispoheader
= cp
= mh_xstrdup(FENDNULL(cp
));
899 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
902 /* change newlines to spaces */
903 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
906 /* trim trailing spaces */
907 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
908 if (!isspace ((unsigned char) *dp
))
913 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
915 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
921 for (dp
= cp
; istoken (*dp
); dp
++)
925 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
929 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
932 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
933 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
935 if (status
== NOTOK
) {
940 inform("extraneous information in message %s's %s: field\n (%s)",
941 ct
->c_file
, DISPO_FIELD
, cp
);
947 ct
->c_dispo
= dispoheader
;
954 get_comment (const char *filename
, const char *fieldname
, char **ap
,
959 char c
, buffer
[BUFSIZ
], *dp
;
969 inform("invalid comment in message %s's %s: field",
970 filename
, fieldname
);
975 if ((c
= *cp
++) == '\0')
998 if ((dp
= *commentp
)) {
999 *commentp
= concat (dp
, " ", buffer
, NULL
);
1002 *commentp
= mh_xstrdup(buffer
);
1006 while (isspace ((unsigned char) *cp
))
1017 * Handles content types audio, image, and video.
1018 * There's not much to do right here.
1026 return OK
; /* not much to do here */
1037 char buffer
[BUFSIZ
];
1042 CI ci
= &ct
->c_ctinfo
;
1044 /* check for missing subtype */
1045 if (!*ci
->ci_subtype
)
1046 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1049 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1051 /* allocate text character set structure */
1053 ct
->c_ctparams
= (void *) t
;
1055 /* scan for charset parameter */
1056 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1057 if (!strcasecmp (pm
->pm_name
, "charset"))
1060 /* check if content specified a character set */
1062 chset
= pm
->pm_value
;
1063 t
->tx_charset
= CHARSET_SPECIFIED
;
1065 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1069 * If we can not handle character set natively,
1070 * then check profile for string to modify the
1071 * terminal or display method.
1073 * termproc is for mhshow, though mhlist -debug prints it, too.
1075 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1076 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1077 if ((cp
= context_find (buffer
)))
1078 ct
->c_termproc
= mh_xstrdup(cp
);
1090 InitMultiPart (CT ct
)
1100 struct multipart
*m
;
1101 struct part
*part
, **next
;
1102 CI ci
= &ct
->c_ctinfo
;
1107 * The encoding for multipart messages must be either
1108 * 7bit, 8bit, or binary (per RFC 2045).
1110 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1111 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1112 /* Copy the Content-Transfer-Encoding header field body so we can
1113 remove any trailing whitespace and leading blanks from it. */
1114 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1116 bp
= cte
+ strlen (cte
) - 1;
1117 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1118 for (bp
= cte
; isblank((unsigned char)*bp
); ++bp
) continue;
1120 inform("\"%s/%s\" type in message %s must be encoded in\n"
1121 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1122 "mhfixmsg -fixcte can fix it, or\n"
1123 "manually edit the file and change the \"%s\"\n"
1124 "Content-Transfer-Encoding to one of those. For now, continuing...",
1125 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1132 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1135 * Check for "boundary" parameter, which is
1136 * required for multipart messages.
1139 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1140 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1146 /* complain if boundary parameter is missing */
1148 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1149 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1153 /* allocate primary structure for multipart info */
1155 ct
->c_ctparams
= (void *) m
;
1157 /* check if boundary parameter contains only whitespace characters */
1158 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1161 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1162 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1166 /* remove trailing whitespace from boundary parameter */
1167 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1168 if (!isspace ((unsigned char) *dp
))
1172 /* record boundary separators */
1173 m
->mp_start
= concat (bp
, "\n", NULL
);
1174 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1176 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1177 advise (ct
->c_file
, "unable to open for reading");
1181 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1183 next
= &m
->mp_parts
;
1187 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1192 if (bufp
[0] != '-' || bufp
[1] != '-')
1195 if (strcmp (bufp
+ 2, m
->mp_start
))
1200 next
= &part
->mp_next
;
1202 if (!(p
= get_content (fp
, ct
->c_file
,
1203 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1211 fseek (fp
, pos
, SEEK_SET
);
1214 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1218 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1219 if (p
->c_end
< p
->c_begin
)
1220 p
->c_begin
= p
->c_end
;
1225 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1230 if (! suppress_bogus_mp_content_warning
) {
1231 inform("bogus multipart content in message %s", ct
->c_file
);
1233 bogus_mp_content
= true;
1235 if (!inout
&& part
) {
1237 p
->c_end
= ct
->c_end
;
1239 if (p
->c_begin
>= p
->c_end
) {
1240 for (next
= &m
->mp_parts
; *next
!= part
;
1241 next
= &((*next
)->mp_next
))
1250 /* reverse the order of the parts for multipart/alternative */
1251 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1257 * label all subparts with part number, and
1258 * then initialize the content of the subpart.
1263 char partnam
[BUFSIZ
];
1266 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1267 pp
= partnam
+ strlen (partnam
);
1272 for (part
= m
->mp_parts
, partnum
= 1; part
;
1273 part
= part
->mp_next
, partnum
++) {
1276 sprintf (pp
, "%d", partnum
);
1277 p
->c_partno
= mh_xstrdup(partnam
);
1279 /* initialize the content of the subparts */
1280 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1289 get_leftover_mp_content (ct
, 1);
1290 get_leftover_mp_content (ct
, 0);
1300 * reverse the order of the parts of a multipart/alternative,
1301 * presumably to put the "most favored" alternative first, for
1302 * ease of choosing/displaying it later on. from a mail message on
1303 * nmh-workers, from kenh:
1304 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1305 * see code in mhn that did the same thing... According to the RCS
1306 * logs, that code was around from the initial checkin of mhn.c by
1307 * John Romine in 1992, which is as far back as we have."
1310 reverse_parts (CT ct
)
1312 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1316 /* Reverse the order of its parts by walking the mp_parts list
1317 and pushing each node to the front. */
1318 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1319 next
= part
->mp_next
;
1320 part
->mp_next
= m
->mp_parts
;
1326 move_preferred_part(CT ct
, mime_type_subtype
*pref
)
1328 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1329 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1333 /* move the matching part(s) to the head of the list: walk the
1334 * list of parts, move matching parts to a new list (maintaining
1335 * their order), and finally, concatenate the old list onto the
1342 head
->mp_next
= m
->mp_parts
;
1343 nhead
->mp_next
= NULL
;
1347 part
= head
->mp_next
;
1348 while (part
!= NULL
) {
1349 ci
= &part
->mp_part
->c_ctinfo
;
1350 if (!strcasecmp(ci
->ci_type
, pref
->type
) &&
1352 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1353 prev
->mp_next
= part
->mp_next
;
1354 part
->mp_next
= NULL
;
1355 ntail
->mp_next
= part
;
1357 part
= prev
->mp_next
;
1360 part
= prev
->mp_next
;
1363 ntail
->mp_next
= head
->mp_next
;
1364 m
->mp_parts
= nhead
->mp_next
;
1368 * move parts that match the user's preferences (-prefer) to the head
1369 * of the line. process preferences in reverse so first one given
1370 * ends up first in line
1376 for (i
= 0; i
< npreferred
; i
++)
1377 move_preferred_part(ct
, mime_preference
+ i
);
1382 /* parse_mime() arranges alternates in reverse (priority) order. This
1383 function can be used to reverse them back. This will put, for
1384 example, a text/plain part before a text/html part in a
1385 multipart/alternative part, for example, where it belongs. */
1387 reverse_alternative_parts (CT ct
)
1389 if (ct
->c_type
== CT_MULTIPART
) {
1390 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1393 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1397 /* And call recursively on each part of a multipart. */
1398 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1399 reverse_alternative_parts (part
->mp_part
);
1412 CI ci
= &ct
->c_ctinfo
;
1414 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1415 inform("\"%s/%s\" type in message %s should be encoded in "
1416 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1421 /* check for missing subtype */
1422 if (!*ci
->ci_subtype
)
1423 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1426 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1428 switch (ct
->c_subtype
) {
1429 case MESSAGE_RFC822
:
1432 case MESSAGE_PARTIAL
:
1438 ct
->c_ctparams
= (void *) p
;
1440 /* scan for parameters "id", "number", and "total" */
1441 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1442 if (!strcasecmp (pm
->pm_name
, "id")) {
1443 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1446 if (!strcasecmp (pm
->pm_name
, "number")) {
1447 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1448 || p
->pm_partno
< 1) {
1450 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1451 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1452 ct
->c_file
, TYPE_FIELD
);
1457 if (!strcasecmp (pm
->pm_name
, "total")) {
1458 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1467 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1468 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1469 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1475 case MESSAGE_EXTERNAL
:
1483 ct
->c_ctparams
= (void *) e
;
1486 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1487 advise (ct
->c_file
, "unable to open for reading");
1491 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1493 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1501 p
->c_ceopenfnx
= NULL
;
1502 if ((exresult
= params_external (ct
, 0)) != NOTOK
1503 && p
->c_ceopenfnx
== openMail
) {
1507 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1509 content_error (NULL
, ct
,
1510 "empty body for access-type=mail-server");
1514 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1515 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1517 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1519 adios ("failed", "fread");
1522 die("unexpected EOF from fread");
1525 bp
+= cc
, size
-= cc
;
1532 p
->c_end
= p
->c_begin
;
1537 if (exresult
== NOTOK
)
1539 if (e
->eb_flags
== NOTOK
)
1542 switch (p
->c_type
) {
1547 if (p
->c_subtype
!= MESSAGE_RFC822
)
1551 e
->eb_partno
= ct
->c_partno
;
1553 (*p
->c_ctinitfnx
) (p
);
1568 params_external (CT ct
, int composing
)
1571 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1572 CI ci
= &ct
->c_ctinfo
;
1574 ct
->c_ceopenfnx
= NULL
;
1575 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1576 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1577 struct str2init
*s2i
;
1578 CT p
= e
->eb_content
;
1580 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1581 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1584 e
->eb_access
= pm
->pm_value
;
1585 e
->eb_flags
= NOTOK
;
1586 p
->c_encoding
= CE_EXTERNAL
;
1589 e
->eb_access
= s2i
->si_key
;
1590 e
->eb_flags
= s2i
->si_val
;
1591 p
->c_encoding
= CE_EXTERNAL
;
1593 /* Call the Init function for this external type */
1594 if ((*s2i
->si_init
)(p
) == NOTOK
)
1598 if (!strcasecmp (pm
->pm_name
, "name")) {
1599 e
->eb_name
= pm
->pm_value
;
1602 if (!strcasecmp (pm
->pm_name
, "permission")) {
1603 e
->eb_permission
= pm
->pm_value
;
1606 if (!strcasecmp (pm
->pm_name
, "site")) {
1607 e
->eb_site
= pm
->pm_value
;
1610 if (!strcasecmp (pm
->pm_name
, "directory")) {
1611 e
->eb_dir
= pm
->pm_value
;
1614 if (!strcasecmp (pm
->pm_name
, "mode")) {
1615 e
->eb_mode
= pm
->pm_value
;
1618 if (!strcasecmp (pm
->pm_name
, "size")) {
1619 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1622 if (!strcasecmp (pm
->pm_name
, "server")) {
1623 e
->eb_server
= pm
->pm_value
;
1626 if (!strcasecmp (pm
->pm_name
, "subject")) {
1627 e
->eb_subject
= pm
->pm_value
;
1630 if (!strcasecmp (pm
->pm_name
, "url")) {
1632 * According to RFC 2017, we have to remove all whitespace from
1636 char *u
, *p
= pm
->pm_value
;
1637 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1639 for (; *p
!= '\0'; p
++) {
1640 if (! isspace((unsigned char) *p
))
1647 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1648 e
->eb_body
= getcpy (pm
->pm_value
);
1653 if (!e
->eb_access
) {
1654 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1655 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1668 InitApplication (CT ct
)
1670 CI ci
= &ct
->c_ctinfo
;
1673 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1680 * TRANSFER ENCODINGS
1684 init_encoding (CT ct
, OpenCEFunc openfnx
)
1686 ct
->c_ceopenfnx
= openfnx
;
1687 ct
->c_ceclosefnx
= close_encoding
;
1688 ct
->c_cesizefnx
= size_encoding
;
1695 close_encoding (CT ct
)
1697 CE ce
= &ct
->c_cefile
;
1706 static unsigned long
1707 size_encoding (CT ct
)
1712 CE ce
= &ct
->c_cefile
;
1715 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1716 return (long) st
.st_size
;
1719 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1720 return (long) st
.st_size
;
1724 if (ct
->c_encoding
== CE_EXTERNAL
)
1725 return ct
->c_end
- ct
->c_begin
;
1728 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1729 return ct
->c_end
- ct
->c_begin
;
1731 if (fstat (fd
, &st
) != NOTOK
)
1732 size
= (long) st
.st_size
;
1736 (*ct
->c_ceclosefnx
) (ct
);
1748 return init_encoding (ct
, openBase64
);
1753 openBase64 (CT ct
, char **file
)
1757 bool own_ct_fp
= false;
1758 char *cp
, *buffer
= NULL
;
1759 /* sbeck -- handle suffixes */
1761 CE ce
= &ct
->c_cefile
;
1762 unsigned char *decoded
;
1764 unsigned char digest
[16];
1767 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1772 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1773 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1779 if (*file
== NULL
) {
1782 ce
->ce_file
= mh_xstrdup(*file
);
1786 /* sbeck@cise.ufl.edu -- handle suffixes */
1788 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1789 if (ce
->ce_unlink
) {
1790 /* Create temporary file with filename extension. */
1791 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1792 die("unable to create temporary file in %s",
1796 ce
->ce_file
= add (cp
, ce
->ce_file
);
1798 } else if (*file
== NULL
) {
1800 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1801 die("unable to create temporary file in %s",
1804 ce
->ce_file
= mh_xstrdup(tempfile
);
1807 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1808 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1812 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1813 die("internal error(1)");
1815 buffer
= mh_xmalloc (len
+ 1);
1818 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1819 content_error (ct
->c_file
, ct
, "unable to open for reading");
1825 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1828 switch (cc
= read (fd
, cp
, len
)) {
1830 content_error (ct
->c_file
, ct
, "error reading from");
1834 content_error (NULL
, ct
, "premature eof");
1845 /* decodeBase64() requires null-terminated input. */
1848 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1849 ct
->c_digested
? digest
: NULL
) != OK
)
1854 unsigned char *decoded_p
= decoded
;
1855 for (i
= 0; i
< decoded_len
; ++i
) {
1856 putc (*decoded_p
++, ce
->ce_fp
);
1859 if (ferror (ce
->ce_fp
)) {
1860 content_error (ce
->ce_file
, ct
, "error writing to");
1864 if (ct
->c_digested
) {
1865 if (memcmp(digest
, ct
->c_digest
,
1867 content_error (NULL
, ct
,
1868 "content integrity suspect (digest mismatch) -- continuing");
1871 fprintf (stderr
, "content integrity confirmed\n");
1877 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1879 if (fflush (ce
->ce_fp
)) {
1880 content_error (ce
->ce_file
, ct
, "error writing to");
1884 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1887 *file
= ce
->ce_file
;
1893 return fileno (ce
->ce_fp
);
1900 free_encoding (ct
, 0);
1910 static char hex2nib
[0x80] = {
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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1915 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1916 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1917 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1918 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1919 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1920 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1921 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1922 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1923 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1924 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1925 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1926 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1933 return init_encoding (ct
, openQuoted
);
1938 openQuoted (CT ct
, char **file
)
1940 int cc
, digested
, len
, quoted
;
1941 bool own_ct_fp
= false;
1947 CE ce
= &ct
->c_cefile
;
1948 /* sbeck -- handle suffixes */
1953 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1958 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1959 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1965 if (*file
== NULL
) {
1968 ce
->ce_file
= mh_xstrdup(*file
);
1972 /* sbeck@cise.ufl.edu -- handle suffixes */
1974 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1975 if (ce
->ce_unlink
) {
1976 /* Create temporary file with filename extension. */
1977 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1978 die("unable to create temporary file in %s",
1982 ce
->ce_file
= add (cp
, ce
->ce_file
);
1984 } else if (*file
== NULL
) {
1986 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1987 die("unable to create temporary file in %s",
1990 ce
->ce_file
= mh_xstrdup(tempfile
);
1993 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1994 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1998 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1999 die("internal error(2)");
2002 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2003 content_error (ct
->c_file
, ct
, "unable to open for reading");
2009 if ((digested
= ct
->c_digested
))
2010 MD5Init (&mdContext
);
2014 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2016 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2017 content_error (NULL
, ct
, "premature eof");
2021 if ((cc
= gotlen
) > len
)
2025 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2026 if (!isspace ((unsigned char) *ep
))
2031 for (; cp
< ep
; cp
++) {
2033 /* in an escape sequence */
2035 /* at byte 1 of an escape sequence */
2036 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2037 /* next is byte 2 */
2040 /* at byte 2 of an escape sequence */
2042 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2043 putc (mask
, ce
->ce_fp
);
2045 MD5Update (&mdContext
, &mask
, 1);
2046 if (ferror (ce
->ce_fp
)) {
2047 content_error (ce
->ce_file
, ct
, "error writing to");
2050 /* finished escape sequence; next may be literal or a new
2051 * escape sequence */
2054 /* on to next byte */
2058 /* not in an escape sequence */
2060 /* starting an escape sequence, or invalid '='? */
2061 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2062 /* "=\n" soft line break, eat the \n */
2066 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2067 /* We don't have 2 bytes left, so this is an invalid
2068 * escape sequence; just show the raw bytes (below). */
2069 } else if (isxdigit ((unsigned char) cp
[1]) &&
2070 isxdigit ((unsigned char) cp
[2])) {
2071 /* Next 2 bytes are hex digits, making this a valid escape
2072 * sequence; let's decode it (above). */
2076 /* One or both of the next 2 is out of range, making this
2077 * an invalid escape sequence; just show the raw bytes
2081 /* Just show the raw byte. */
2082 putc (*cp
, ce
->ce_fp
);
2085 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2087 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2090 if (ferror (ce
->ce_fp
)) {
2091 content_error (ce
->ce_file
, ct
, "error writing to");
2097 content_error (NULL
, ct
,
2098 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2102 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2104 if (fflush (ce
->ce_fp
)) {
2105 content_error (ce
->ce_file
, ct
, "error writing to");
2110 unsigned char digest
[16];
2112 MD5Final (digest
, &mdContext
);
2113 if (memcmp(digest
, ct
->c_digest
,
2115 content_error (NULL
, ct
,
2116 "content integrity suspect (digest mismatch) -- continuing");
2118 fprintf (stderr
, "content integrity confirmed\n");
2121 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2124 *file
= ce
->ce_file
;
2130 return fileno (ce
->ce_fp
);
2133 free_encoding (ct
, 0);
2150 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2153 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2159 open7Bit (CT ct
, char **file
)
2162 bool own_ct_fp
= false;
2163 char buffer
[BUFSIZ
];
2164 /* sbeck -- handle suffixes */
2167 CE ce
= &ct
->c_cefile
;
2170 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2175 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2176 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2182 if (*file
== NULL
) {
2185 ce
->ce_file
= mh_xstrdup(*file
);
2189 /* sbeck@cise.ufl.edu -- handle suffixes */
2191 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2192 if (ce
->ce_unlink
) {
2193 /* Create temporary file with filename extension. */
2194 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2195 die("unable to create temporary file in %s",
2199 ce
->ce_file
= add (cp
, ce
->ce_file
);
2201 } else if (*file
== NULL
) {
2203 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2204 die("unable to create temporary file in %s",
2207 ce
->ce_file
= mh_xstrdup(tempfile
);
2210 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2211 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2215 if (ct
->c_type
== CT_MULTIPART
) {
2216 CI ci
= &ct
->c_ctinfo
;
2220 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2221 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2222 + 1 + strlen (ci
->ci_subtype
);
2223 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2226 fputs (buffer
, ce
->ce_fp
);
2230 if (ci
->ci_comment
) {
2231 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2232 fputs ("\n\t", ce
->ce_fp
);
2236 putc (' ', ce
->ce_fp
);
2239 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2242 fprintf (ce
->ce_fp
, "\n");
2244 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2246 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2248 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2249 fprintf (ce
->ce_fp
, "\n");
2252 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2253 die("internal error(3)");
2256 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2257 content_error (ct
->c_file
, ct
, "unable to open for reading");
2263 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2265 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2267 content_error (ct
->c_file
, ct
, "error reading from");
2271 content_error (NULL
, ct
, "premature eof");
2279 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2280 advise ("open7Bit", "fwrite");
2282 if (ferror (ce
->ce_fp
)) {
2283 content_error (ce
->ce_file
, ct
, "error writing to");
2288 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2290 if (fflush (ce
->ce_fp
)) {
2291 content_error (ce
->ce_file
, ct
, "error writing to");
2295 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2298 *file
= ce
->ce_file
;
2303 return fileno (ce
->ce_fp
);
2306 free_encoding (ct
, 0);
2320 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2322 char cachefile
[BUFSIZ
];
2325 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2330 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2331 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2337 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2338 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2339 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2340 ce
->ce_file
= mh_xstrdup(cachefile
);
2344 admonish (cachefile
, "unable to fopen for reading");
2347 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2351 *file
= ce
->ce_file
;
2352 *fd
= fileno (ce
->ce_fp
);
2363 return init_encoding (ct
, openFile
);
2368 openFile (CT ct
, char **file
)
2371 char cachefile
[BUFSIZ
];
2372 struct exbody
*e
= ct
->c_ctexbody
;
2373 CE ce
= &ct
->c_cefile
;
2375 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2387 content_error (NULL
, ct
, "missing name parameter");
2391 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2394 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2395 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2399 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2400 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2401 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2405 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2406 if ((fp
= fopen (cachefile
, "w"))) {
2408 char buffer
[BUFSIZ
];
2409 FILE *gp
= ce
->ce_fp
;
2411 fseek (gp
, 0L, SEEK_SET
);
2413 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2415 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2416 advise ("openFile", "fwrite");
2421 admonish (ce
->ce_file
, "error reading");
2422 (void) m_unlink (cachefile
);
2423 } else if (ferror (fp
)) {
2424 admonish (cachefile
, "error writing");
2425 (void) m_unlink (cachefile
);
2432 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2433 *file
= ce
->ce_file
;
2434 return fileno (ce
->ce_fp
);
2444 return init_encoding (ct
, openFTP
);
2449 openFTP (CT ct
, char **file
)
2455 char *bp
, *ftp
, *user
, *pass
;
2456 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2458 CE ce
= &ct
->c_cefile
;
2459 static char *username
= NULL
;
2460 static char *password
= NULL
;
2464 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2470 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2481 if (!e
->eb_name
|| !e
->eb_site
) {
2482 content_error (NULL
, ct
, "missing %s parameter",
2483 e
->eb_name
? "site": "name");
2487 /* Get the buffer ready to go */
2489 buflen
= sizeof(buffer
);
2492 * Construct the query message for user
2494 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2500 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2506 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2507 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2512 if (e
->eb_size
> 0) {
2513 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2518 snprintf (bp
, buflen
, "? ");
2521 * Now, check the answer
2523 if (!read_yes_or_no_if_tty (buffer
))
2528 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2532 ruserpass (e
->eb_site
, &username
, &password
, 0);
2537 ce
->ce_unlink
= (*file
== NULL
);
2539 cachefile
[0] = '\0';
2540 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2541 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2542 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2543 if (*file
== NULL
) {
2550 ce
->ce_file
= mh_xstrdup(*file
);
2552 ce
->ce_file
= mh_xstrdup(cachefile
);
2555 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2556 die("unable to create temporary file in %s",
2559 ce
->ce_file
= mh_xstrdup(tempfile
);
2562 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2563 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2572 vec
[vecp
++] = r1bindex (ftp
, '/');
2573 vec
[vecp
++] = e
->eb_site
;
2576 vec
[vecp
++] = e
->eb_dir
;
2577 vec
[vecp
++] = e
->eb_name
;
2578 vec
[vecp
++] = ce
->ce_file
,
2579 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2580 ? "ascii" : "binary";
2588 adios ("fork", "unable to");
2592 close (fileno (ce
->ce_fp
));
2594 fprintf (stderr
, "unable to exec ");
2600 if (pidXwait (child_id
, NULL
)) {
2601 username
= password
= NULL
;
2611 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2616 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2617 if ((fp
= fopen (cachefile
, "w"))) {
2619 FILE *gp
= ce
->ce_fp
;
2621 fseek (gp
, 0L, SEEK_SET
);
2623 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2625 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2626 advise ("openFTP", "fwrite");
2631 admonish (ce
->ce_file
, "error reading");
2632 (void) m_unlink (cachefile
);
2633 } else if (ferror (fp
)) {
2634 admonish (cachefile
, "error writing");
2635 (void) m_unlink (cachefile
);
2643 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2644 *file
= ce
->ce_file
;
2645 return fileno (ce
->ce_fp
);
2656 return init_encoding (ct
, openMail
);
2661 openMail (CT ct
, char **file
)
2663 int child_id
, fd
, vecp
;
2665 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2666 struct exbody
*e
= ct
->c_ctexbody
;
2667 CE ce
= &ct
->c_cefile
;
2669 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2680 if (!e
->eb_server
) {
2681 content_error (NULL
, ct
, "missing server parameter");
2685 /* Get buffer ready to go */
2687 buflen
= sizeof(buffer
);
2689 /* Now, construct query message */
2690 snprintf (bp
, buflen
, "Retrieve content");
2696 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2702 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2704 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2706 /* Now, check answer */
2707 if (!read_yes_or_no_if_tty (buffer
))
2711 vec
[vecp
++] = r1bindex (mailproc
, '/');
2712 vec
[vecp
++] = e
->eb_server
;
2713 vec
[vecp
++] = "-subject";
2714 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2715 vec
[vecp
++] = "-body";
2716 vec
[vecp
++] = e
->eb_body
;
2722 advise ("fork", "unable to");
2726 execvp (mailproc
, vec
);
2727 fprintf (stderr
, "unable to exec ");
2733 if (pidXwait (child_id
, NULL
) == OK
)
2734 inform("request sent");
2738 if (*file
== NULL
) {
2740 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2741 die("unable to create temporary file in %s",
2744 ce
->ce_file
= mh_xstrdup(tempfile
);
2747 ce
->ce_file
= mh_xstrdup(*file
);
2751 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2752 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2756 /* showproc is for mhshow and mhstore, though mhlist -debug
2757 * prints it, too. */
2758 free(ct
->c_showproc
);
2759 ct
->c_showproc
= mh_xstrdup("true");
2761 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2762 *file
= ce
->ce_file
;
2763 return fileno (ce
->ce_fp
);
2774 return init_encoding (ct
, openURL
);
2779 openURL (CT ct
, char **file
)
2781 struct exbody
*e
= ct
->c_ctexbody
;
2782 CE ce
= &ct
->c_cefile
;
2783 char *urlprog
, *program
;
2784 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2788 struct msgs_array args
= { 0, 0, NULL
};
2791 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2795 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2799 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2811 content_error(NULL
, ct
, "missing url parameter");
2815 ce
->ce_unlink
= (*file
== NULL
);
2817 cachefile
[0] = '\0';
2819 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2820 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2821 if (*file
== NULL
) {
2828 ce
->ce_file
= mh_xstrdup(*file
);
2830 ce
->ce_file
= mh_xstrdup(cachefile
);
2833 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2834 die("unable to create temporary file in %s",
2837 ce
->ce_file
= mh_xstrdup(tempfile
);
2840 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2841 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2845 switch (child_id
= fork()) {
2847 adios ("fork", "unable to");
2851 argsplit_msgarg(&args
, urlprog
, &program
);
2852 app_msgarg(&args
, e
->eb_url
);
2853 app_msgarg(&args
, NULL
);
2854 dup2(fileno(ce
->ce_fp
), 1);
2855 close(fileno(ce
->ce_fp
));
2856 execvp(program
, args
.msgs
);
2857 fprintf(stderr
, "Unable to exec ");
2863 if (pidXwait(child_id
, NULL
)) {
2871 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2876 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2877 if ((fp
= fopen(cachefile
, "w"))) {
2879 FILE *gp
= ce
->ce_fp
;
2881 fseeko(gp
, 0, SEEK_SET
);
2883 while ((cc
= fread(buffer
, sizeof(*buffer
),
2884 sizeof(buffer
), gp
)) > 0)
2885 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2886 advise ("openURL", "fwrite");
2892 admonish(ce
->ce_file
, "error reading");
2893 (void) m_unlink (cachefile
);
2900 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2901 *file
= ce
->ce_file
;
2902 return fileno(ce
->ce_fp
);
2907 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2908 * has to be base64 decoded.
2911 readDigest (CT ct
, char *cp
)
2913 unsigned char *digest
;
2916 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2917 const size_t maxlen
= sizeof ct
->c_digest
;
2919 if (strlen ((char *) digest
) <= maxlen
) {
2920 memcpy (ct
->c_digest
, digest
, maxlen
);
2925 fprintf (stderr
, "MD5 digest=");
2926 for (i
= 0; i
< maxlen
; ++i
) {
2927 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2929 fprintf (stderr
, "\n");
2935 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2936 (int) strlen ((char *) digest
));
2946 /* Multipart parts might have content before the first subpart and/or
2947 after the last subpart that hasn't been stored anywhere else, so do
2950 get_leftover_mp_content (CT ct
, int before
/* or after */)
2952 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2954 bool found_boundary
= false;
2960 char *content
= NULL
;
2962 if (! m
) return NOTOK
;
2965 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2967 /* Isolate the beginning of this part to the beginning of the
2968 first subpart and save any content between them. */
2969 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2970 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2971 boundary
= concat ("--", m
->mp_start
, NULL
);
2973 struct part
*last_subpart
= NULL
;
2974 struct part
*subpart
;
2976 /* Go to the last subpart to get its end position. */
2977 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2978 last_subpart
= subpart
;
2981 if (last_subpart
== NULL
) return NOTOK
;
2983 /* Isolate the end of the last subpart to the end of this part
2984 and save any content between them. */
2985 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2986 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2987 boundary
= concat ("--", m
->mp_stop
, NULL
);
2990 /* Back up by 1 to pick up the newline. */
2991 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2993 /* Don't look beyond beginning of first subpart (before) or
2994 next part (after). */
2995 if (read
> max
) bufp
[read
-max
] = '\0';
2998 if (! strcmp (bufp
, boundary
)) {
2999 found_boundary
= true;
3002 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3003 found_boundary
= true;
3008 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3010 char *old_content
= content
;
3011 content
= concat (content
, bufp
, NULL
);
3015 ? concat ("\n", bufp
, NULL
)
3016 : concat (bufp
, NULL
);
3021 if (found_boundary
|| read
> max
) break;
3023 if (read
> max
) break;
3027 /* Skip the newline if that's all there is. */
3031 /* Remove trailing newline, except at EOF. */
3032 if ((before
|| ! feof (ct
->c_fp
)) &&
3033 (cp
= content
+ strlen (content
)) > content
&&
3038 if (strlen (content
) > 1) {
3040 m
->mp_content_before
= content
;
3042 m
->mp_content_after
= content
;
3057 ct_type_str (int type
)
3060 case CT_APPLICATION
:
3061 return "application";
3077 return "unknown_type";
3083 ct_subtype_str (int type
, int subtype
)
3086 case CT_APPLICATION
:
3088 case APPLICATION_OCTETS
:
3090 case APPLICATION_POSTSCRIPT
:
3091 return "postscript";
3093 return "unknown_app_subtype";
3097 case MESSAGE_RFC822
:
3099 case MESSAGE_PARTIAL
:
3101 case MESSAGE_EXTERNAL
:
3104 return "unknown_msg_subtype";
3110 case MULTI_ALTERNATE
:
3111 return "alternative";
3114 case MULTI_PARALLEL
:
3119 return "unknown_multipart_subtype";
3130 return "unknown_text_subtype";
3133 return "unknown_type";
3139 ct_str_type (const char *type
)
3141 struct str2init
*s2i
;
3143 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3144 if (! strcasecmp (type
, s2i
->si_key
)) {
3148 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3157 ct_str_subtype (int type
, const char *subtype
)
3162 case CT_APPLICATION
:
3163 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3164 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3168 return kv
->kv_value
;
3170 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3171 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3175 return kv
->kv_value
;
3177 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3178 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3182 return kv
->kv_value
;
3184 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3185 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3189 return kv
->kv_value
;
3196 /* Find the content type and InitFunc for the CT. */
3197 const struct str2init
*
3198 get_ct_init (int type
)
3200 const struct str2init
*sp
;
3202 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3203 if (type
== sp
->si_val
) {
3212 ce_str (int encoding
)
3218 return "quoted-printable";
3234 /* Find the content type and InitFunc for the content encoding method. */
3235 const struct str2init
*
3236 get_ce_method (const char *method
)
3238 struct str2init
*sp
;
3240 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3241 if (! strcasecmp (method
, sp
->si_key
)) {
3250 * Parse a series of MIME attributes (or parameters) given a header as
3253 * Arguments include:
3255 * filename - Name of input file (for error messages)
3256 * fieldname - Name of field being processed
3257 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3258 * Updated to point to end of attributes when finished.
3259 * param_head - Pointer to head of parameter list
3260 * param_tail - Pointer to tail of parameter list
3261 * commentp - Pointer to header comment pointer (may be NULL)
3263 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3264 * DONE to indicate a benign error (minor parsing error, but the program
3269 parse_header_attrs (const char *filename
, const char *fieldname
,
3270 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3273 char *cp
= *header_attrp
;
3279 struct sectlist
*next
;
3285 struct sectlist
*sechead
;
3286 struct parmlist
*next
;
3287 } *pp
, *pp2
, *phead
= NULL
;
3289 while (*cp
== ';') {
3290 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3291 bool encoded
= false;
3292 bool partial
= false;
3293 int len
= 0, index
= 0;
3296 while (isspace ((unsigned char) *cp
))
3300 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3305 if (! suppress_extraneous_trailing_semicolon_warning
) {
3306 inform("extraneous trailing ';' in message %s's %s: "
3307 "parameter list", filename
, fieldname
);
3312 /* down case the attribute name */
3313 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3314 *dp
= tolower ((unsigned char) *dp
);
3316 for (up
= dp
; isspace ((unsigned char) *dp
);)
3318 if (dp
== cp
|| *dp
!= '=') {
3319 inform("invalid parameter in message %s's %s: field\n"
3320 " parameter %s (error detected at offset %ld)",
3321 filename
, fieldname
, cp
, (long)(dp
- cp
));
3326 * To handle RFC 2231, we have to deal with the following extensions:
3328 * name*=encoded-value
3329 * name*<N>=part-N-of-a-parameter-value
3330 * name*<N>*=encoded-part-N-of-a-parameter-value
3333 * If there's a * right before the equal sign, it's encoded.
3334 * If there's a * and one or more digits, then it's section N.
3336 * Remember we can have one or the other, or both. cp points to
3337 * beginning of name, up points past the last character in the
3341 for (vp
= cp
; vp
< up
; vp
++) {
3342 if (*vp
== '*' && vp
< up
- 1) {
3346 if (*vp
== '*' && vp
== up
- 1) {
3348 } else if (partial
) {
3349 if (isdigit((unsigned char) *vp
))
3350 index
= *vp
- '0' + index
* 10;
3352 inform("invalid parameter index in message %s's %s: field"
3353 "\n (parameter %s)", filename
, fieldname
, cp
);
3362 * Break out the parameter name and value sections and allocate
3366 nameptr
= mh_xmalloc(len
+ 1);
3367 strncpy(nameptr
, cp
, len
);
3368 nameptr
[len
] = '\0';
3370 for (dp
++; isspace ((unsigned char) *dp
);)
3375 * Single quotes delimit the character set and language tag.
3376 * They are required on the first section (or a complete
3381 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3387 charset
= mh_xmalloc(len
+ 1);
3388 strncpy(charset
, dp
, len
);
3389 charset
[len
] = '\0';
3395 inform("missing charset in message %s's %s: field\n"
3396 " (parameter %s)", filename
, fieldname
, nameptr
);
3402 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3409 lang
= mh_xmalloc(len
+ 1);
3410 strncpy(lang
, dp
, len
);
3417 inform("missing language tag in message %s's %s: field\n"
3418 " (parameter %s)", filename
, fieldname
, nameptr
);
3428 * At this point vp should be pointing at the beginning
3429 * of the encoded value/section. Continue until we reach
3430 * the end or get whitespace. But first, calculate the
3431 * length so we can allocate the correct buffer size.
3434 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3436 if (*(vp
+ 1) == '\0' ||
3437 !isxdigit((unsigned char) *(vp
+ 1)) ||
3438 *(vp
+ 2) == '\0' ||
3439 !isxdigit((unsigned char) *(vp
+ 2))) {
3440 inform("invalid encoded sequence in message %s's %s: field\n"
3441 " (parameter %s)", filename
, fieldname
, nameptr
);
3452 up
= valptr
= mh_xmalloc(len
+ 1);
3454 for (vp
= dp
; istoken(*vp
); vp
++) {
3456 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3467 * A "normal" string. If it's got a leading quote, then we
3468 * strip the quotes out. Otherwise go until we reach the end
3469 * or get whitespace. Note we scan it twice; once to get the
3470 * length, then the second time copies it into the destination
3477 for (cp
= dp
+ 1;;) {
3481 inform("invalid quoted-string in message %s's %s: field\n"
3482 " (parameter %s)", filename
, fieldname
, nameptr
);
3502 for (cp
= dp
; istoken (*cp
); cp
++) {
3507 valptr
= mh_xmalloc(len
+ 1);
3511 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3519 strncpy(valptr
, cp
= dp
, len
);
3527 * If 'partial' is set, we don't allocate a parameter now. We
3528 * put it on the parameter linked list to be reassembled later.
3530 * "phead" points to a list of all parameters we need to reassemble.
3531 * Each parameter has a list of sections. We insert the sections in
3536 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3537 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3552 * Insert this into the section linked list
3560 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3561 sp
->next
= pp
->sechead
;
3564 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3565 if (sp2
->index
== sp
->index
) {
3566 inform("duplicate index (%d) in message %s's %s: field"
3567 "\n (parameter %s)", sp
->index
, filename
,
3568 fieldname
, nameptr
);
3571 if (sp2
->index
< sp
->index
&&
3572 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3573 sp
->next
= sp2
->next
;
3580 inform("Internal error: cannot insert partial param "
3581 "in message %s's %s: field\n (parameter %s)",
3582 filename
, fieldname
, nameptr
);
3588 * Save our charset and lang tags.
3591 if (index
== 0 && encoded
) {
3593 pp
->charset
= charset
;
3598 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3599 pm
->pm_charset
= charset
;
3603 while (isspace ((unsigned char) *cp
))
3607 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3613 * Now that we're done, reassemble all of the partial parameters.
3616 for (pp
= phead
; pp
!= NULL
; ) {
3620 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3621 if (sp
->index
!= pindex
++) {
3622 inform("missing section %d for parameter in message "
3623 "%s's %s: field\n (parameter %s)", pindex
- 1,
3624 filename
, fieldname
, pp
->name
);
3630 p
= q
= mh_xmalloc(tlen
+ 1);
3631 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3632 memcpy(q
, sp
->value
, sp
->len
);
3642 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3643 pm
->pm_charset
= pp
->charset
;
3644 pm
->pm_lang
= pp
->lang
;
3655 * Return the charset for a particular content type.
3659 content_charset (CT ct
)
3661 char *ret_charset
= NULL
;
3663 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3665 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3670 * Create a string based on a list of output parameters. Assume that this
3671 * parameter string will be appended to an existing header, so start out
3672 * with the separator (;). Perform RFC 2231 encoding when necessary.
3676 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3678 char *paramout
= NULL
;
3679 char line
[CPERLIN
* 2], *q
;
3680 int curlen
, index
, cont
, encode
, i
;
3681 size_t valoff
, numchars
;
3683 while (params
!= NULL
) {
3689 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3692 if (strlen(params
->pm_name
) > CPERLIN
) {
3693 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3698 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3701 * Loop until we get a parameter that fits within a line. We
3702 * assume new lines start with a tab, so check our overflow based
3712 * At this point we're definitely continuing the line, so
3713 * be sure to include the parameter name and section index.
3716 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3717 params
->pm_name
, index
);
3720 * Both of these functions do a NUL termination
3724 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3725 numchars
, valoff
, index
);
3727 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3737 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3742 * "line" starts with a ;\n\t, so that doesn't count against
3743 * the length. But add 8 since it starts with a tab; that's
3744 * how we end up with 5.
3747 initialwidth
= strlen(line
) + 5;
3750 * At this point the line should be built, so add it to our
3751 * current output buffer.
3754 paramout
= add(line
, paramout
);
3758 * If this won't fit on the line, start a new one. Save room in
3759 * case we need a semicolon on the end
3762 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3774 * At this point, we're either finishing a continued parameter, or
3775 * we're working on a new one.
3779 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3780 params
->pm_name
, index
);
3782 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3787 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3788 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3790 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3791 strlen(params
->pm_value
+ valoff
), valoff
);
3798 paramout
= add(line
, paramout
);
3799 initialwidth
+= strlen(line
);
3801 params
= params
->pm_next
;
3805 *offsetout
= initialwidth
;
3811 * Calculate the size of a parameter.
3815 * pm - The parameter being output
3816 * index - If continuing the parameter, the index of the section
3818 * valueoff - The current offset into the parameter value that we're
3819 * working on (previous sections have consumed valueoff bytes).
3820 * encode - Set if we should perform encoding on this parameter section
3821 * (given that we're consuming bytesfit bytes).
3822 * cont - Set if the remaining data in value will not fit on a single
3823 * line and will need to be continued.
3824 * bytesfit - The number of bytes that we can consume from the parameter
3825 * value and still fit on a completely new line. The
3826 * calculation assumes the new line starts with a tab,
3827 * includes the parameter name and any encoding, and fits
3828 * within CPERLIN bytes. Will always be at least 1.
3832 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3835 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3836 size_t len
= 0, fit
= 0;
3837 int fitlimit
= 0, eightbit
, maxfit
;
3842 * Add up the length. First, start with the parameter name.
3845 len
= strlen(pm
->pm_name
);
3848 * Scan the parameter value and see if we need to do encoding for this
3852 eightbit
= contains8bit(start
, NULL
);
3855 * Determine if we need to encode this section. Encoding is necessary if:
3857 * - There are any 8-bit characters at all and we're on the first
3859 * - There are 8-bit characters within N bytes of our section start.
3860 * N is calculated based on the number of bytes it would take to
3861 * reach CPERLIN. Specifically:
3862 * 8 (starting tab) +
3863 * strlen(param name) +
3864 * 4 ('* for section marker, '=', opening/closing '"')
3866 * is the number of bytes used by everything that isn't part of the
3867 * value. So that gets subtracted from CPERLIN.
3870 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3871 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3872 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3876 len
++; /* Add in equal sign */
3880 * We're using maxfit as a marker for how many characters we can
3881 * fit into the line. Bump it by two because we're not using quotes
3888 * If we don't have a charset or language tag in this parameter,
3892 if (! pm
->pm_charset
) {
3893 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3894 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3895 die("8-bit characters in parameter \"%s\", but "
3896 "local character set is US-ASCII", pm
->pm_name
);
3899 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3901 len
++; /* For the encoding marker */
3904 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3909 * We know we definitely need to include an index. maxfit already
3910 * includes the section marker.
3912 len
+= strlen(indexchar
);
3914 for (p
= start
; *p
!= '\0'; p
++) {
3915 if (isparamencode(*p
)) {
3923 * Just so there's no confusion: maxfit is counting OUTPUT
3924 * characters (post-encoding). fit is counting INPUT characters.
3926 if (! fitlimit
&& maxfit
>= 0)
3928 else if (! fitlimit
)
3933 * Calculate the string length, but add room for quoting \
3934 * and " if necessary. Also account for quotes at beginning
3937 for (p
= start
; *p
!= '\0'; p
++) {
3948 if (! fitlimit
&& maxfit
>= 0)
3950 else if (! fitlimit
)
3967 * Output an encoded parameter string.
3971 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3972 size_t valueoff
, int index
)
3974 size_t outlen
= 0, n
;
3975 char *endptr
= output
+ len
, *p
;
3978 * First, output the marker for an encoded string.
3986 * If the index is 0, output the character set and language tag.
3987 * If theses were NULL, they should have already been filled in
3992 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3996 if (output
> endptr
) {
3997 inform("Internal error: parameter buffer overflow");
4003 * Copy over the value, encoding if necessary
4006 p
= pm
->pm_value
+ valueoff
;
4007 while (valuelen
-- > 0) {
4008 if (isparamencode(*p
)) {
4009 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4016 if (output
> endptr
) {
4017 inform("Internal error: parameter buffer overflow");
4028 * Output a "normal" parameter, without encoding. Be sure to escape
4029 * quotes and backslashes if necessary.
4033 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4037 char *endptr
= output
+ len
, *p
;
4043 p
= pm
->pm_value
+ valueoff
;
4045 while (valuelen
-- > 0) {
4056 if (output
> endptr
) {
4057 inform("Internal error: parameter buffer overflow");
4062 if (output
- 2 > endptr
) {
4063 inform("Internal error: parameter buffer overflow");
4074 * Add a parameter to the parameter linked list
4078 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4083 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4084 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4087 (*last
)->pm_next
= pm
;
4098 * Either replace a current parameter with a new value, or add the parameter
4099 * to the parameter linked list.
4103 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4107 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4108 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4110 * If nocopy is set, it's assumed that we own both name
4111 * and value. We don't need name, so we discard it now.
4116 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4121 return add_param(first
, last
, name
, value
, nocopy
);
4125 * Retrieve a parameter value from a parameter linked list. If the parameter
4126 * value needs converted to the local character set, do that now.
4130 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4132 while (first
!= NULL
) {
4133 if (strcasecmp(name
, first
->pm_name
) == 0) {
4135 return first
->pm_value
;
4136 return getcpy(get_param_value(first
, replace
));
4138 first
= first
->pm_next
;
4145 * Return a parameter value, converting to the local character set if
4150 get_param_value(PM pm
, char replace
)
4152 static char buffer
[4096]; /* I hope no parameters are larger */
4153 size_t bufsize
= sizeof(buffer
);
4158 ICONV_CONST
char *p
;
4159 #else /* HAVE_ICONV */
4161 #endif /* HAVE_ICONV */
4166 * If we don't have a character set indicated, it's assumed to be
4167 * US-ASCII. If it matches our character set, we don't need to convert
4171 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4172 strlen(pm
->pm_charset
))) {
4173 return pm
->pm_value
;
4177 * In this case, we need to convert. If we have iconv support, use
4178 * that. Otherwise, go through and simply replace every non-ASCII
4179 * character with the substitution character.
4184 bufsize
= sizeof(buffer
);
4185 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4187 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4188 if (cd
== (iconv_t
) -1) {
4192 inbytes
= strlen(pm
->pm_value
);
4196 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4197 if (errno
!= EILSEQ
) {
4202 * Reset shift state, substitute our character,
4203 * try to restart conversion.
4206 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4219 for (++p
, --inbytes
;
4220 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4239 #endif /* HAVE_ICONV */
4242 * Take everything non-ASCII and substitute the replacement character
4246 bufsize
= sizeof(buffer
);
4247 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4248 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))