1 /* mhparse.c -- routines to parse the contents of MIME messages
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
9 #include "sbr/arglist.h"
10 #include "sbr/error.h"
16 #include "h/mhparse.h"
19 #include "h/mhcachesbr.h"
20 #include "sbr/m_mktemp.h"
24 #endif /* HAVE_ICONV */
25 #include "sbr/base64.h"
30 int checksw
= 0; /* check Content-MD5 field */
33 * These are for mhfixmsg to:
34 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
36 * 2) Suppress the warning about bogus multipart content, and report it.
37 * 3) Suppress the warning about extraneous trailing ';' in header parameter
40 bool skip_mp_cte_check
;
41 bool suppress_bogus_mp_content_warning
;
42 bool bogus_mp_content
;
43 bool suppress_extraneous_trailing_semicolon_warning
;
46 * By default, suppress warning about multiple MIME-Version header fields.
48 bool suppress_multiple_mime_version_warning
= true;
50 /* list of preferred type/subtype pairs, for -prefer */
51 mime_type_subtype mime_preference
[NPREFS
];
56 * Structures for TEXT messages
58 struct k2v SubText
[] = {
59 { "plain", TEXT_PLAIN
},
60 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
61 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
62 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
65 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
68 * Structures for MULTIPART messages
70 struct k2v SubMultiPart
[] = {
71 { "mixed", MULTI_MIXED
},
72 { "alternative", MULTI_ALTERNATE
},
73 { "digest", MULTI_DIGEST
},
74 { "parallel", MULTI_PARALLEL
},
75 { "related", MULTI_RELATED
},
76 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
80 * Structures for MESSAGE messages
82 struct k2v SubMessage
[] = {
83 { "rfc822", MESSAGE_RFC822
},
84 { "partial", MESSAGE_PARTIAL
},
85 { "external-body", MESSAGE_EXTERNAL
},
86 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
90 * Structure for APPLICATION messages
92 struct k2v SubApplication
[] = {
93 { "octet-stream", APPLICATION_OCTETS
},
94 { "postscript", APPLICATION_POSTSCRIPT
},
95 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
99 * Mapping of names of CTE types in mhbuild directives
101 static struct k2v EncodingType
[] = {
104 { "q-p", CE_QUOTED
},
105 { "quoted-printable", CE_QUOTED
},
106 { "b64", CE_BASE64
},
107 { "base64", CE_BASE64
},
115 static CT
get_content (FILE *, char *, int);
116 static int get_comment (const char *, const char *, char **, char **);
118 static int InitGeneric (CT
);
119 static int InitText (CT
);
120 static int InitMultiPart (CT
);
121 static void reverse_parts (CT
);
122 static void prefer_parts(CT ct
);
123 static int InitMessage (CT
);
124 static int InitApplication (CT
);
125 static int init_encoding (CT
, OpenCEFunc
);
126 static unsigned long size_encoding (CT
);
127 static int InitBase64 (CT
);
128 static int openBase64 (CT
, char **);
129 static int InitQuoted (CT
);
130 static int openQuoted (CT
, char **);
131 static int Init7Bit (CT
);
132 static int openExternal (CT
, CT
, CE
, char **, int *);
133 static int InitFile (CT
);
134 static int openFile (CT
, char **);
135 static int InitFTP (CT
);
136 static int openFTP (CT
, char **);
137 static int InitMail (CT
);
138 static int openMail (CT
, char **);
139 static int readDigest (CT
, char *);
140 static int get_leftover_mp_content (CT
, int);
141 static int InitURL (CT
);
142 static int openURL (CT
, char **);
143 static int parse_header_attrs (const char *, const char *, char **, PM
*,
145 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
146 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
147 static int get_dispo (char *, CT
, int);
149 struct str2init str2cts
[] = {
150 { "application", CT_APPLICATION
, InitApplication
},
151 { "audio", CT_AUDIO
, InitGeneric
},
152 { "image", CT_IMAGE
, InitGeneric
},
153 { "message", CT_MESSAGE
, InitMessage
},
154 { "multipart", CT_MULTIPART
, InitMultiPart
},
155 { "text", CT_TEXT
, InitText
},
156 { "video", CT_VIDEO
, InitGeneric
},
157 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
158 { NULL
, CT_UNKNOWN
, NULL
},
161 struct str2init str2ces
[] = {
162 { "base64", CE_BASE64
, InitBase64
},
163 { "quoted-printable", CE_QUOTED
, InitQuoted
},
164 { "8bit", CE_8BIT
, Init7Bit
},
165 { "7bit", CE_7BIT
, Init7Bit
},
166 { "binary", CE_BINARY
, Init7Bit
},
167 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
168 { NULL
, CE_UNKNOWN
, NULL
},
172 * NOTE WELL: si_key MUST NOT have value of NOTOK
174 * si_val is 1 if access method is anonymous.
176 struct str2init str2methods
[] = {
177 { "afs", 1, InitFile
},
178 { "anon-ftp", 1, InitFTP
},
179 { "ftp", 0, InitFTP
},
180 { "local-file", 0, InitFile
},
181 { "mail-server", 0, InitMail
},
182 { "url", 0, InitURL
},
188 * Main entry point for parsing a MIME message or file.
189 * It returns the Content structure for the top level
190 * entity in the file.
194 parse_mime (char *file
)
203 bogus_mp_content
= false;
206 * Check if file is actually standard input
208 if ((is_stdin
= !(strcmp (file
, "-")))) {
209 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
211 advise("mhparse", "unable to create temporary file in %s",
215 file
= mh_xstrdup(tfile
);
217 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
218 if (fwrite(buffer
, 1, n
, fp
) != n
) {
219 (void) m_unlink (file
);
220 advise (file
, "error copying to temporary file");
226 if (ferror (stdin
)) {
227 (void) m_unlink (file
);
228 advise ("stdin", "error reading");
232 (void) m_unlink (file
);
233 advise (file
, "error writing");
236 fseek (fp
, 0L, SEEK_SET
);
237 } else if (stat (file
, &statbuf
) == NOTOK
) {
238 advise (file
, "unable to stat");
240 } else if (S_ISDIR(statbuf
.st_mode
)) {
241 /* Don't try to parse a directory. */
242 inform("%s is a directory", file
);
244 } else if ((fp
= fopen (file
, "r")) == NULL
) {
245 advise (file
, "unable to read");
249 if (!(ct
= get_content (fp
, file
, 1))) {
251 (void) m_unlink (file
);
252 inform("unable to decode %s", file
);
257 ct
->c_unlink
= 1; /* temp file to remove */
261 if (ct
->c_end
== 0L) {
262 fseek (fp
, 0L, SEEK_END
);
263 ct
->c_end
= ftell (fp
);
266 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
278 * Main routine for reading/parsing the headers
279 * of a message content.
281 * toplevel = 1 # we are at the top level of the message
282 * toplevel = 0 # we are inside message type or multipart type
283 * # other than multipart/digest
284 * toplevel = -1 # we are inside multipart/digest
285 * NB: on failure we will fclose(in)!
289 get_content (FILE *in
, char *file
, int toplevel
)
292 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
296 m_getfld_state_t gstate
;
298 /* allocate the content structure */
301 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
302 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
305 * Parse the header fields for this
306 * content into a linked list.
308 gstate
= m_getfld_state_init(in
);
309 m_getfld_track_filepos2(&gstate
);
310 for (compnum
= 1;;) {
311 int bufsz
= sizeof buf
;
312 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
317 /* get copies of the buffers */
318 np
= mh_xstrdup(name
);
319 vp
= mh_xstrdup(buf
);
321 /* if necessary, get rest of field */
322 while (state
== FLDPLUS
) {
324 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
325 vp
= add (buf
, vp
); /* add to previous value */
328 /* Now add the header data to the list */
329 add_header (ct
, np
, vp
);
331 /* continue, to see if this isn't the last header field */
332 ct
->c_begin
= ftell (in
) + 1;
336 /* There are two cases. The unusual one is when there is no
337 * blank line between the headers and the body. This is
338 * indicated by the name of the header starting with `:'.
340 * For both cases, normal first, `1' is the desired c_begin
341 * file position for the start of the body, and `2' is the
342 * file position when buf is returned.
344 * f o o : b a r \n \n b o d y \n bufsz = 6
346 * f o o : b a r \n b o d y \n bufsz = 4
349 * For the normal case, bufsz includes the
350 * header-terminating `\n', even though it is not in buf,
351 * but bufsz isn't affected when it's missing in the unusual
353 if (name
[0] == ':') {
354 ct
->c_begin
= ftell(in
) - bufsz
;
356 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
361 ct
->c_begin
= ftell (in
);
366 die("message format error in component #%d", compnum
);
369 die("getfld() returned %d", state
);
372 /* break out of the loop */
375 m_getfld_state_destroy (&gstate
);
378 * Read the content headers. We will parse the
379 * MIME related header fields into their various
380 * structures and set internal flags related to
381 * content type/subtype, etc.
384 hp
= ct
->c_first_hf
; /* start at first header field */
386 /* Get MIME-Version field */
387 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
392 vrsn
= mh_xstrdup(FENDNULL(hp
->value
));
394 /* Now, cleanup this field */
397 while (isspace ((unsigned char) *cp
))
399 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
401 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
402 if (!isspace ((unsigned char) *dp
))
406 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
409 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
412 for (dp
= cp
; istoken (*dp
); dp
++)
416 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
419 inform("message %s has unknown value for %s: field (%s), continuing...",
420 ct
->c_file
, VRSN_FIELD
, cp
);
425 if (! suppress_multiple_mime_version_warning
)
426 inform("message %s has multiple %s: fields",
427 ct
->c_file
, VRSN_FIELD
);
431 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
432 /* Get Content-Type field */
433 struct str2init
*s2i
;
434 CI ci
= &ct
->c_ctinfo
;
436 /* Check if we've already seen a Content-Type header */
438 inform("message %s has multiple %s: fields",
439 ct
->c_file
, TYPE_FIELD
);
443 /* Parse the Content-Type field */
444 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
448 * Set the Init function and the internal
449 * flag for this content type.
451 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
452 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
454 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
456 ct
->c_type
= s2i
->si_val
;
457 ct
->c_ctinitfnx
= s2i
->si_init
;
459 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
460 /* Get Content-Transfer-Encoding field */
462 struct str2init
*s2i
;
465 * Check if we've already seen the
466 * Content-Transfer-Encoding field
469 inform("message %s has multiple %s: fields",
470 ct
->c_file
, ENCODING_FIELD
);
474 /* get copy of this field */
475 ct
->c_celine
= cp
= mh_xstrdup(FENDNULL(hp
->value
));
477 while (isspace ((unsigned char) *cp
))
479 for (dp
= cp
; istoken (*dp
); dp
++)
485 * Find the internal flag and Init function
486 * for this transfer encoding.
488 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
489 if (!strcasecmp (cp
, s2i
->si_key
))
491 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
494 ct
->c_encoding
= s2i
->si_val
;
496 /* Call the Init function for this encoding */
497 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
500 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
501 /* Get Content-MD5 field */
507 if (ct
->c_digested
) {
508 inform("message %s has multiple %s: fields",
509 ct
->c_file
, MD5_FIELD
);
513 ep
= cp
= mh_xstrdup(FENDNULL(hp
->value
)); /* get a copy */
515 while (isspace ((unsigned char) *cp
))
517 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
519 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
520 if (!isspace ((unsigned char) *dp
))
524 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
527 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
532 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
540 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
541 /* Get Content-ID field */
542 ct
->c_id
= add (hp
->value
, ct
->c_id
);
544 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
545 /* Get Content-Description field */
546 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
548 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
549 /* Get Content-Disposition field */
550 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
555 hp
= hp
->next
; /* next header field */
559 * Check if we saw a Content-Type field.
560 * If not, then assign a default value for
561 * it, and the Init function.
565 * If we are inside a multipart/digest message,
566 * so default type is message/rfc822
569 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
571 ct
->c_type
= CT_MESSAGE
;
572 ct
->c_ctinitfnx
= InitMessage
;
575 * Else default type is text/plain
577 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
579 ct
->c_type
= CT_TEXT
;
580 ct
->c_ctinitfnx
= InitText
;
584 /* Use default Transfer-Encoding, if necessary */
586 ct
->c_encoding
= CE_7BIT
;
599 * small routine to add header field to list
603 add_header (CT ct
, char *name
, char *value
)
607 /* allocate header field structure */
610 /* link data into header structure */
615 /* link header structure into the list */
616 if (ct
->c_first_hf
== NULL
) {
617 ct
->c_first_hf
= hp
; /* this is the first */
620 ct
->c_last_hf
->next
= hp
; /* add it to the end */
629 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
630 * directives. Fills in the information of the CTinfo structure.
633 get_ctinfo (char *cp
, CT ct
, int magic
)
642 /* store copy of Content-Type line */
643 cp
= ct
->c_ctline
= mh_xstrdup(FENDNULL(cp
));
645 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
648 /* change newlines to spaces */
649 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
652 /* trim trailing spaces */
653 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
654 if (!isspace ((unsigned char) *dp
))
659 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
661 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
662 &ci
->ci_comment
) == NOTOK
)
665 for (dp
= cp
; istoken (*dp
); dp
++)
669 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
674 inform("invalid %s: field in message %s (empty type)",
675 TYPE_FIELD
, ct
->c_file
);
678 to_lower(ci
->ci_type
);
680 while (isspace ((unsigned char) *cp
))
683 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
684 &ci
->ci_comment
) == NOTOK
)
689 ci
->ci_subtype
= mh_xstrdup("");
694 while (isspace ((unsigned char) *cp
))
697 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
698 &ci
->ci_comment
) == NOTOK
)
701 for (dp
= cp
; istoken (*dp
); dp
++)
705 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
709 if (!*ci
->ci_subtype
) {
710 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
711 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
714 to_lower(ci
->ci_subtype
);
717 while (isspace ((unsigned char) *cp
))
720 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
721 &ci
->ci_comment
) == NOTOK
)
724 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
725 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
726 &ci
->ci_comment
)) != OK
) {
727 return status
== NOTOK
? NOTOK
: OK
;
731 * Get any <Content-Id> given in buffer
733 if (magic
&& *cp
== '<') {
736 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
737 inform("invalid ID in message %s", ct
->c_file
);
743 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
749 while (isspace ((unsigned char) *cp
))
754 * Get any [Content-Description] given in buffer.
756 if (magic
&& *cp
== '[') {
758 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
762 inform("invalid description in message %s", ct
->c_file
);
770 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
776 while (isspace ((unsigned char) *cp
))
781 * Get any {Content-Disposition} given in buffer.
783 if (magic
&& *cp
== '{') {
785 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
789 inform("invalid disposition in message %s", ct
->c_file
);
797 if (get_dispo(cp
, ct
, 1) != OK
)
803 while (isspace ((unsigned char) *cp
))
808 * Get any extension directives (right now just the content transfer
809 * encoding, but maybe others) that we care about.
812 if (magic
&& *cp
== '*') {
814 * See if it's a CTE we match on
819 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
823 inform("invalid null transfer encoding specification");
830 ct
->c_reqencoding
= CE_UNKNOWN
;
832 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
833 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
834 ct
->c_reqencoding
= kv
->kv_value
;
839 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
840 inform("invalid CTE specification: \"%s\"", dp
);
844 while (isspace ((unsigned char) *cp
))
849 * Check if anything is left over
853 ci
->ci_magic
= mh_xstrdup(cp
);
855 /* If there is a Content-Disposition header and it doesn't
856 have a *filename=, extract it from the magic contents.
857 The r1bindex call skips any leading directory
859 if (ct
->c_dispo_type
&&
860 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
861 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
862 r1bindex(ci
->ci_magic
, '/'), 0);
866 inform("extraneous information in message %s's %s: field\n"
867 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
875 * Parse out a Content-Disposition header. A lot of this is cribbed from
879 get_dispo (char *cp
, CT ct
, int buildflag
)
881 char *dp
, *dispoheader
;
886 * Save the whole copy of the Content-Disposition header, unless we're
887 * processing a mhbuild directive. A NULL c_dispo will be a flag to
888 * mhbuild that the disposition header needs to be generated at that
892 dispoheader
= cp
= mh_xstrdup(FENDNULL(cp
));
894 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
897 /* change newlines to spaces */
898 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
901 /* trim trailing spaces */
902 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
903 if (!isspace ((unsigned char) *dp
))
908 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
910 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
916 for (dp
= cp
; istoken (*dp
); dp
++)
920 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
924 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
927 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
928 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
930 if (status
== NOTOK
) {
935 inform("extraneous information in message %s's %s: field\n (%s)",
936 ct
->c_file
, DISPO_FIELD
, cp
);
942 ct
->c_dispo
= dispoheader
;
949 get_comment (const char *filename
, const char *fieldname
, char **ap
,
954 char c
, buffer
[BUFSIZ
], *dp
;
964 inform("invalid comment in message %s's %s: field",
965 filename
, fieldname
);
970 if ((c
= *cp
++) == '\0')
993 if ((dp
= *commentp
)) {
994 *commentp
= concat (dp
, " ", buffer
, NULL
);
997 *commentp
= mh_xstrdup(buffer
);
1001 while (isspace ((unsigned char) *cp
))
1012 * Handles content types audio, image, and video.
1013 * There's not much to do right here.
1021 return OK
; /* not much to do here */
1032 char buffer
[BUFSIZ
];
1037 CI ci
= &ct
->c_ctinfo
;
1039 /* check for missing subtype */
1040 if (!*ci
->ci_subtype
)
1041 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1044 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1046 /* allocate text character set structure */
1048 ct
->c_ctparams
= (void *) t
;
1050 /* scan for charset parameter */
1051 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1052 if (!strcasecmp (pm
->pm_name
, "charset"))
1055 /* check if content specified a character set */
1057 chset
= pm
->pm_value
;
1058 t
->tx_charset
= CHARSET_SPECIFIED
;
1060 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1064 * If we can not handle character set natively,
1065 * then check profile for string to modify the
1066 * terminal or display method.
1068 * termproc is for mhshow, though mhlist -debug prints it, too.
1070 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1071 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1072 if ((cp
= context_find (buffer
)))
1073 ct
->c_termproc
= mh_xstrdup(cp
);
1085 InitMultiPart (CT ct
)
1095 struct multipart
*m
;
1096 struct part
*part
, **next
;
1097 CI ci
= &ct
->c_ctinfo
;
1102 * The encoding for multipart messages must be either
1103 * 7bit, 8bit, or binary (per RFC 2045).
1105 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1106 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1107 /* Copy the Content-Transfer-Encoding header field body so we can
1108 remove any trailing whitespace and leading blanks from it. */
1109 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1111 bp
= cte
+ strlen (cte
) - 1;
1112 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1113 for (bp
= cte
; isblank((unsigned char)*bp
); ++bp
) continue;
1115 inform("\"%s/%s\" type in message %s must be encoded in\n"
1116 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1117 "mhfixmsg -fixcte can fix it, or\n"
1118 "manually edit the file and change the \"%s\"\n"
1119 "Content-Transfer-Encoding to one of those. For now, continuing...",
1120 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1127 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1130 * Check for "boundary" parameter, which is
1131 * required for multipart messages.
1134 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1135 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1141 /* complain if boundary parameter is missing */
1143 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1144 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1148 /* allocate primary structure for multipart info */
1150 ct
->c_ctparams
= (void *) m
;
1152 /* check if boundary parameter contains only whitespace characters */
1153 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1156 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1157 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1161 /* remove trailing whitespace from boundary parameter */
1162 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1163 if (!isspace ((unsigned char) *dp
))
1167 /* record boundary separators */
1168 m
->mp_start
= concat (bp
, "\n", NULL
);
1169 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1171 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1172 advise (ct
->c_file
, "unable to open for reading");
1176 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1178 next
= &m
->mp_parts
;
1182 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1187 if (bufp
[0] != '-' || bufp
[1] != '-')
1190 if (strcmp (bufp
+ 2, m
->mp_start
))
1195 next
= &part
->mp_next
;
1197 if (!(p
= get_content (fp
, ct
->c_file
,
1198 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1206 fseek (fp
, pos
, SEEK_SET
);
1209 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1213 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1214 if (p
->c_end
< p
->c_begin
)
1215 p
->c_begin
= p
->c_end
;
1220 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1225 if (! suppress_bogus_mp_content_warning
) {
1226 inform("bogus multipart content in message %s", ct
->c_file
);
1228 bogus_mp_content
= true;
1230 if (!inout
&& part
) {
1232 p
->c_end
= ct
->c_end
;
1234 if (p
->c_begin
>= p
->c_end
) {
1235 for (next
= &m
->mp_parts
; *next
!= part
;
1236 next
= &((*next
)->mp_next
))
1245 /* reverse the order of the parts for multipart/alternative */
1246 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1252 * label all subparts with part number, and
1253 * then initialize the content of the subpart.
1258 char partnam
[BUFSIZ
];
1261 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1262 pp
= partnam
+ strlen (partnam
);
1267 for (part
= m
->mp_parts
, partnum
= 1; part
;
1268 part
= part
->mp_next
, partnum
++) {
1271 sprintf (pp
, "%d", partnum
);
1272 p
->c_partno
= mh_xstrdup(partnam
);
1274 /* initialize the content of the subparts */
1275 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1284 get_leftover_mp_content (ct
, 1);
1285 get_leftover_mp_content (ct
, 0);
1295 * reverse the order of the parts of a multipart/alternative,
1296 * presumably to put the "most favored" alternative first, for
1297 * ease of choosing/displaying it later on. from a mail message on
1298 * nmh-workers, from kenh:
1299 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1300 * see code in mhn that did the same thing... According to the RCS
1301 * logs, that code was around from the initial checkin of mhn.c by
1302 * John Romine in 1992, which is as far back as we have."
1305 reverse_parts (CT ct
)
1307 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1311 /* Reverse the order of its parts by walking the mp_parts list
1312 and pushing each node to the front. */
1313 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1314 next
= part
->mp_next
;
1315 part
->mp_next
= m
->mp_parts
;
1321 move_preferred_part(CT ct
, mime_type_subtype
*pref
)
1323 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1324 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1328 /* move the matching part(s) to the head of the list: walk the
1329 * list of parts, move matching parts to a new list (maintaining
1330 * their order), and finally, concatenate the old list onto the
1337 head
->mp_next
= m
->mp_parts
;
1338 nhead
->mp_next
= NULL
;
1342 part
= head
->mp_next
;
1343 while (part
!= NULL
) {
1344 ci
= &part
->mp_part
->c_ctinfo
;
1345 if (!strcasecmp(ci
->ci_type
, pref
->type
) &&
1347 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1348 prev
->mp_next
= part
->mp_next
;
1349 part
->mp_next
= NULL
;
1350 ntail
->mp_next
= part
;
1352 part
= prev
->mp_next
;
1355 part
= prev
->mp_next
;
1358 ntail
->mp_next
= head
->mp_next
;
1359 m
->mp_parts
= nhead
->mp_next
;
1363 * move parts that match the user's preferences (-prefer) to the head
1364 * of the line. process preferences in reverse so first one given
1365 * ends up first in line
1371 for (i
= 0; i
< npreferred
; i
++)
1372 move_preferred_part(ct
, mime_preference
+ i
);
1377 /* parse_mime() arranges alternates in reverse (priority) order. This
1378 function can be used to reverse them back. This will put, for
1379 example, a text/plain part before a text/html part in a
1380 multipart/alternative part, for example, where it belongs. */
1382 reverse_alternative_parts (CT ct
)
1384 if (ct
->c_type
== CT_MULTIPART
) {
1385 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1388 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1392 /* And call recursively on each part of a multipart. */
1393 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1394 reverse_alternative_parts (part
->mp_part
);
1407 CI ci
= &ct
->c_ctinfo
;
1409 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1410 inform("\"%s/%s\" type in message %s should be encoded in "
1411 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1416 /* check for missing subtype */
1417 if (!*ci
->ci_subtype
)
1418 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1421 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1423 switch (ct
->c_subtype
) {
1424 case MESSAGE_RFC822
:
1427 case MESSAGE_PARTIAL
:
1433 ct
->c_ctparams
= (void *) p
;
1435 /* scan for parameters "id", "number", and "total" */
1436 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1437 if (!strcasecmp (pm
->pm_name
, "id")) {
1438 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1441 if (!strcasecmp (pm
->pm_name
, "number")) {
1442 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1443 || p
->pm_partno
< 1) {
1445 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1446 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1447 ct
->c_file
, TYPE_FIELD
);
1452 if (!strcasecmp (pm
->pm_name
, "total")) {
1453 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1462 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1463 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1464 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1470 case MESSAGE_EXTERNAL
:
1478 ct
->c_ctparams
= (void *) e
;
1481 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1482 advise (ct
->c_file
, "unable to open for reading");
1486 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1488 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1496 p
->c_ceopenfnx
= NULL
;
1497 if ((exresult
= params_external (ct
, 0)) != NOTOK
1498 && p
->c_ceopenfnx
== openMail
) {
1502 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1504 content_error (NULL
, ct
,
1505 "empty body for access-type=mail-server");
1509 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1510 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1512 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1514 adios ("failed", "fread");
1517 die("unexpected EOF from fread");
1520 bp
+= cc
, size
-= cc
;
1527 p
->c_end
= p
->c_begin
;
1532 if (exresult
== NOTOK
)
1534 if (e
->eb_flags
== NOTOK
)
1537 switch (p
->c_type
) {
1542 if (p
->c_subtype
!= MESSAGE_RFC822
)
1546 e
->eb_partno
= ct
->c_partno
;
1548 (*p
->c_ctinitfnx
) (p
);
1563 params_external (CT ct
, int composing
)
1566 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1567 CI ci
= &ct
->c_ctinfo
;
1569 ct
->c_ceopenfnx
= NULL
;
1570 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1571 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1572 struct str2init
*s2i
;
1573 CT p
= e
->eb_content
;
1575 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1576 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1579 e
->eb_access
= pm
->pm_value
;
1580 e
->eb_flags
= NOTOK
;
1581 p
->c_encoding
= CE_EXTERNAL
;
1584 e
->eb_access
= s2i
->si_key
;
1585 e
->eb_flags
= s2i
->si_val
;
1586 p
->c_encoding
= CE_EXTERNAL
;
1588 /* Call the Init function for this external type */
1589 if ((*s2i
->si_init
)(p
) == NOTOK
)
1593 if (!strcasecmp (pm
->pm_name
, "name")) {
1594 e
->eb_name
= pm
->pm_value
;
1597 if (!strcasecmp (pm
->pm_name
, "permission")) {
1598 e
->eb_permission
= pm
->pm_value
;
1601 if (!strcasecmp (pm
->pm_name
, "site")) {
1602 e
->eb_site
= pm
->pm_value
;
1605 if (!strcasecmp (pm
->pm_name
, "directory")) {
1606 e
->eb_dir
= pm
->pm_value
;
1609 if (!strcasecmp (pm
->pm_name
, "mode")) {
1610 e
->eb_mode
= pm
->pm_value
;
1613 if (!strcasecmp (pm
->pm_name
, "size")) {
1614 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1617 if (!strcasecmp (pm
->pm_name
, "server")) {
1618 e
->eb_server
= pm
->pm_value
;
1621 if (!strcasecmp (pm
->pm_name
, "subject")) {
1622 e
->eb_subject
= pm
->pm_value
;
1625 if (!strcasecmp (pm
->pm_name
, "url")) {
1627 * According to RFC 2017, we have to remove all whitespace from
1631 char *u
, *p
= pm
->pm_value
;
1632 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1634 for (; *p
!= '\0'; p
++) {
1635 if (! isspace((unsigned char) *p
))
1642 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1643 e
->eb_body
= getcpy (pm
->pm_value
);
1648 if (!e
->eb_access
) {
1649 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1650 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1663 InitApplication (CT ct
)
1665 CI ci
= &ct
->c_ctinfo
;
1668 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1675 * TRANSFER ENCODINGS
1679 init_encoding (CT ct
, OpenCEFunc openfnx
)
1681 ct
->c_ceopenfnx
= openfnx
;
1682 ct
->c_ceclosefnx
= close_encoding
;
1683 ct
->c_cesizefnx
= size_encoding
;
1690 close_encoding (CT ct
)
1692 CE ce
= &ct
->c_cefile
;
1701 static unsigned long
1702 size_encoding (CT ct
)
1707 CE ce
= &ct
->c_cefile
;
1710 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1711 return (long) st
.st_size
;
1714 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1715 return (long) st
.st_size
;
1719 if (ct
->c_encoding
== CE_EXTERNAL
)
1720 return ct
->c_end
- ct
->c_begin
;
1723 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1724 return ct
->c_end
- ct
->c_begin
;
1726 if (fstat (fd
, &st
) != NOTOK
)
1727 size
= (long) st
.st_size
;
1731 (*ct
->c_ceclosefnx
) (ct
);
1743 return init_encoding (ct
, openBase64
);
1748 openBase64 (CT ct
, char **file
)
1752 bool own_ct_fp
= false;
1753 char *cp
, *buffer
= NULL
;
1754 /* sbeck -- handle suffixes */
1756 CE ce
= &ct
->c_cefile
;
1757 unsigned char *decoded
;
1759 unsigned char digest
[16];
1762 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1767 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1768 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1774 if (*file
== NULL
) {
1777 ce
->ce_file
= mh_xstrdup(*file
);
1781 /* sbeck@cise.ufl.edu -- handle suffixes */
1783 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1784 if (ce
->ce_unlink
) {
1785 /* Create temporary file with filename extension. */
1786 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1787 die("unable to create temporary file in %s",
1791 ce
->ce_file
= add (cp
, ce
->ce_file
);
1793 } else if (*file
== NULL
) {
1795 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1796 die("unable to create temporary file in %s",
1799 ce
->ce_file
= mh_xstrdup(tempfile
);
1802 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1803 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1807 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1808 die("internal error(1)");
1810 buffer
= mh_xmalloc (len
+ 1);
1813 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1814 content_error (ct
->c_file
, ct
, "unable to open for reading");
1820 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1823 switch (cc
= read (fd
, cp
, len
)) {
1825 content_error (ct
->c_file
, ct
, "error reading from");
1829 content_error (NULL
, ct
, "premature eof");
1840 /* decodeBase64() requires null-terminated input. */
1843 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1844 ct
->c_digested
? digest
: NULL
) != OK
)
1849 unsigned char *decoded_p
= decoded
;
1850 for (i
= 0; i
< decoded_len
; ++i
) {
1851 putc (*decoded_p
++, ce
->ce_fp
);
1854 if (ferror (ce
->ce_fp
)) {
1855 content_error (ce
->ce_file
, ct
, "error writing to");
1859 if (ct
->c_digested
) {
1860 if (memcmp(digest
, ct
->c_digest
,
1862 content_error (NULL
, ct
,
1863 "content integrity suspect (digest mismatch) -- continuing");
1866 fprintf (stderr
, "content integrity confirmed\n");
1872 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1874 if (fflush (ce
->ce_fp
)) {
1875 content_error (ce
->ce_file
, ct
, "error writing to");
1879 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1882 *file
= ce
->ce_file
;
1888 return fileno (ce
->ce_fp
);
1895 free_encoding (ct
, 0);
1905 static char hex2nib
[0x80] = {
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1909 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1910 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1911 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1912 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1913 0x08, 0x09, 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,
1918 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1919 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1920 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1921 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1928 return init_encoding (ct
, openQuoted
);
1933 openQuoted (CT ct
, char **file
)
1935 int cc
, digested
, len
, quoted
;
1936 bool own_ct_fp
= false;
1942 CE ce
= &ct
->c_cefile
;
1943 /* sbeck -- handle suffixes */
1948 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1953 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1954 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1960 if (*file
== NULL
) {
1963 ce
->ce_file
= mh_xstrdup(*file
);
1967 /* sbeck@cise.ufl.edu -- handle suffixes */
1969 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1970 if (ce
->ce_unlink
) {
1971 /* Create temporary file with filename extension. */
1972 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1973 die("unable to create temporary file in %s",
1977 ce
->ce_file
= add (cp
, ce
->ce_file
);
1979 } else if (*file
== NULL
) {
1981 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1982 die("unable to create temporary file in %s",
1985 ce
->ce_file
= mh_xstrdup(tempfile
);
1988 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1989 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1993 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1994 die("internal error(2)");
1997 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1998 content_error (ct
->c_file
, ct
, "unable to open for reading");
2004 if ((digested
= ct
->c_digested
))
2005 MD5Init (&mdContext
);
2009 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2011 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2012 content_error (NULL
, ct
, "premature eof");
2016 if ((cc
= gotlen
) > len
)
2020 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2021 if (!isspace ((unsigned char) *ep
))
2026 for (; cp
< ep
; cp
++) {
2028 /* in an escape sequence */
2030 /* at byte 1 of an escape sequence */
2031 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2032 /* next is byte 2 */
2035 /* at byte 2 of an escape sequence */
2037 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2038 putc (mask
, ce
->ce_fp
);
2040 MD5Update (&mdContext
, &mask
, 1);
2041 if (ferror (ce
->ce_fp
)) {
2042 content_error (ce
->ce_file
, ct
, "error writing to");
2045 /* finished escape sequence; next may be literal or a new
2046 * escape sequence */
2049 /* on to next byte */
2053 /* not in an escape sequence */
2055 /* starting an escape sequence, or invalid '='? */
2056 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2057 /* "=\n" soft line break, eat the \n */
2061 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2062 /* We don't have 2 bytes left, so this is an invalid
2063 * escape sequence; just show the raw bytes (below). */
2064 } else if (isxdigit ((unsigned char) cp
[1]) &&
2065 isxdigit ((unsigned char) cp
[2])) {
2066 /* Next 2 bytes are hex digits, making this a valid escape
2067 * sequence; let's decode it (above). */
2071 /* One or both of the next 2 is out of range, making this
2072 * an invalid escape sequence; just show the raw bytes
2076 /* Just show the raw byte. */
2077 putc (*cp
, ce
->ce_fp
);
2080 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2082 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2085 if (ferror (ce
->ce_fp
)) {
2086 content_error (ce
->ce_file
, ct
, "error writing to");
2092 content_error (NULL
, ct
,
2093 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2097 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2099 if (fflush (ce
->ce_fp
)) {
2100 content_error (ce
->ce_file
, ct
, "error writing to");
2105 unsigned char digest
[16];
2107 MD5Final (digest
, &mdContext
);
2108 if (memcmp(digest
, ct
->c_digest
,
2110 content_error (NULL
, ct
,
2111 "content integrity suspect (digest mismatch) -- continuing");
2113 fprintf (stderr
, "content integrity confirmed\n");
2116 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2119 *file
= ce
->ce_file
;
2125 return fileno (ce
->ce_fp
);
2128 free_encoding (ct
, 0);
2145 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2148 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2154 open7Bit (CT ct
, char **file
)
2157 bool own_ct_fp
= false;
2158 char buffer
[BUFSIZ
];
2159 /* sbeck -- handle suffixes */
2162 CE ce
= &ct
->c_cefile
;
2165 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2170 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2171 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2177 if (*file
== NULL
) {
2180 ce
->ce_file
= mh_xstrdup(*file
);
2184 /* sbeck@cise.ufl.edu -- handle suffixes */
2186 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2187 if (ce
->ce_unlink
) {
2188 /* Create temporary file with filename extension. */
2189 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2190 die("unable to create temporary file in %s",
2194 ce
->ce_file
= add (cp
, ce
->ce_file
);
2196 } else if (*file
== NULL
) {
2198 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2199 die("unable to create temporary file in %s",
2202 ce
->ce_file
= mh_xstrdup(tempfile
);
2205 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2206 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2210 if (ct
->c_type
== CT_MULTIPART
) {
2211 CI ci
= &ct
->c_ctinfo
;
2215 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2216 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2217 + 1 + strlen (ci
->ci_subtype
);
2218 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2221 fputs (buffer
, ce
->ce_fp
);
2225 if (ci
->ci_comment
) {
2226 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2227 fputs ("\n\t", ce
->ce_fp
);
2231 putc (' ', ce
->ce_fp
);
2234 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2237 fprintf (ce
->ce_fp
, "\n");
2239 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2241 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2243 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2244 fprintf (ce
->ce_fp
, "\n");
2247 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2248 die("internal error(3)");
2251 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2252 content_error (ct
->c_file
, ct
, "unable to open for reading");
2258 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2260 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2262 content_error (ct
->c_file
, ct
, "error reading from");
2266 content_error (NULL
, ct
, "premature eof");
2274 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2275 advise ("open7Bit", "fwrite");
2277 if (ferror (ce
->ce_fp
)) {
2278 content_error (ce
->ce_file
, ct
, "error writing to");
2283 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2285 if (fflush (ce
->ce_fp
)) {
2286 content_error (ce
->ce_file
, ct
, "error writing to");
2290 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2293 *file
= ce
->ce_file
;
2298 return fileno (ce
->ce_fp
);
2301 free_encoding (ct
, 0);
2315 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2317 char cachefile
[BUFSIZ
];
2320 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2325 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2326 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2332 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2333 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2334 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2335 ce
->ce_file
= mh_xstrdup(cachefile
);
2339 admonish (cachefile
, "unable to fopen for reading");
2342 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2346 *file
= ce
->ce_file
;
2347 *fd
= fileno (ce
->ce_fp
);
2358 return init_encoding (ct
, openFile
);
2363 openFile (CT ct
, char **file
)
2366 char cachefile
[BUFSIZ
];
2367 struct exbody
*e
= ct
->c_ctexbody
;
2368 CE ce
= &ct
->c_cefile
;
2370 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2382 content_error (NULL
, ct
, "missing name parameter");
2386 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2389 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2390 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2394 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2395 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2396 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2400 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2401 if ((fp
= fopen (cachefile
, "w"))) {
2403 char buffer
[BUFSIZ
];
2404 FILE *gp
= ce
->ce_fp
;
2406 fseek (gp
, 0L, SEEK_SET
);
2408 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2410 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2411 advise ("openFile", "fwrite");
2416 admonish (ce
->ce_file
, "error reading");
2417 (void) m_unlink (cachefile
);
2418 } else if (ferror (fp
)) {
2419 admonish (cachefile
, "error writing");
2420 (void) m_unlink (cachefile
);
2427 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2428 *file
= ce
->ce_file
;
2429 return fileno (ce
->ce_fp
);
2439 return init_encoding (ct
, openFTP
);
2444 openFTP (CT ct
, char **file
)
2450 char *bp
, *ftp
, *user
, *pass
;
2451 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2453 CE ce
= &ct
->c_cefile
;
2454 static char *username
= NULL
;
2455 static char *password
= NULL
;
2459 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2465 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2476 if (!e
->eb_name
|| !e
->eb_site
) {
2477 content_error (NULL
, ct
, "missing %s parameter",
2478 e
->eb_name
? "site": "name");
2482 /* Get the buffer ready to go */
2484 buflen
= sizeof(buffer
);
2487 * Construct the query message for user
2489 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2495 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2501 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2502 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2507 if (e
->eb_size
> 0) {
2508 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2513 snprintf (bp
, buflen
, "? ");
2516 * Now, check the answer
2518 if (!read_yes_or_no_if_tty (buffer
))
2523 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2527 ruserpass (e
->eb_site
, &username
, &password
, 0);
2532 ce
->ce_unlink
= (*file
== NULL
);
2534 cachefile
[0] = '\0';
2535 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2536 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2537 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2538 if (*file
== NULL
) {
2545 ce
->ce_file
= mh_xstrdup(*file
);
2547 ce
->ce_file
= mh_xstrdup(cachefile
);
2550 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2551 die("unable to create temporary file in %s",
2554 ce
->ce_file
= mh_xstrdup(tempfile
);
2557 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2558 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2567 vec
[vecp
++] = r1bindex (ftp
, '/');
2568 vec
[vecp
++] = e
->eb_site
;
2571 vec
[vecp
++] = e
->eb_dir
;
2572 vec
[vecp
++] = e
->eb_name
;
2573 vec
[vecp
++] = ce
->ce_file
,
2574 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2575 ? "ascii" : "binary";
2583 adios ("fork", "unable to");
2587 close (fileno (ce
->ce_fp
));
2589 fprintf (stderr
, "unable to exec ");
2595 if (pidXwait (child_id
, NULL
)) {
2596 username
= password
= NULL
;
2606 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2611 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2612 if ((fp
= fopen (cachefile
, "w"))) {
2614 FILE *gp
= ce
->ce_fp
;
2616 fseek (gp
, 0L, SEEK_SET
);
2618 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2620 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2621 advise ("openFTP", "fwrite");
2626 admonish (ce
->ce_file
, "error reading");
2627 (void) m_unlink (cachefile
);
2628 } else if (ferror (fp
)) {
2629 admonish (cachefile
, "error writing");
2630 (void) m_unlink (cachefile
);
2638 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2639 *file
= ce
->ce_file
;
2640 return fileno (ce
->ce_fp
);
2651 return init_encoding (ct
, openMail
);
2656 openMail (CT ct
, char **file
)
2658 int child_id
, fd
, vecp
;
2660 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2661 struct exbody
*e
= ct
->c_ctexbody
;
2662 CE ce
= &ct
->c_cefile
;
2664 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2675 if (!e
->eb_server
) {
2676 content_error (NULL
, ct
, "missing server parameter");
2680 /* Get buffer ready to go */
2682 buflen
= sizeof(buffer
);
2684 /* Now, construct query message */
2685 snprintf (bp
, buflen
, "Retrieve content");
2691 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2697 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2699 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2701 /* Now, check answer */
2702 if (!read_yes_or_no_if_tty (buffer
))
2706 vec
[vecp
++] = r1bindex (mailproc
, '/');
2707 vec
[vecp
++] = e
->eb_server
;
2708 vec
[vecp
++] = "-subject";
2709 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2710 vec
[vecp
++] = "-body";
2711 vec
[vecp
++] = e
->eb_body
;
2717 advise ("fork", "unable to");
2721 execvp (mailproc
, vec
);
2722 fprintf (stderr
, "unable to exec ");
2728 if (pidXwait (child_id
, NULL
) == OK
)
2729 inform("request sent");
2733 if (*file
== NULL
) {
2735 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2736 die("unable to create temporary file in %s",
2739 ce
->ce_file
= mh_xstrdup(tempfile
);
2742 ce
->ce_file
= mh_xstrdup(*file
);
2746 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2747 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2751 /* showproc is for mhshow and mhstore, though mhlist -debug
2752 * prints it, too. */
2753 free(ct
->c_showproc
);
2754 ct
->c_showproc
= mh_xstrdup("true");
2756 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2757 *file
= ce
->ce_file
;
2758 return fileno (ce
->ce_fp
);
2769 return init_encoding (ct
, openURL
);
2774 openURL (CT ct
, char **file
)
2776 struct exbody
*e
= ct
->c_ctexbody
;
2777 CE ce
= &ct
->c_cefile
;
2778 char *urlprog
, *program
;
2779 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2783 struct msgs_array args
= { 0, 0, NULL
};
2786 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2790 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2794 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2806 content_error(NULL
, ct
, "missing url parameter");
2810 ce
->ce_unlink
= (*file
== NULL
);
2812 cachefile
[0] = '\0';
2814 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2815 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2816 if (*file
== NULL
) {
2823 ce
->ce_file
= mh_xstrdup(*file
);
2825 ce
->ce_file
= mh_xstrdup(cachefile
);
2828 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2829 die("unable to create temporary file in %s",
2832 ce
->ce_file
= mh_xstrdup(tempfile
);
2835 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2836 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2840 switch (child_id
= fork()) {
2842 adios ("fork", "unable to");
2846 argsplit_msgarg(&args
, urlprog
, &program
);
2847 app_msgarg(&args
, e
->eb_url
);
2848 app_msgarg(&args
, NULL
);
2849 dup2(fileno(ce
->ce_fp
), 1);
2850 close(fileno(ce
->ce_fp
));
2851 execvp(program
, args
.msgs
);
2852 fprintf(stderr
, "Unable to exec ");
2858 if (pidXwait(child_id
, NULL
)) {
2866 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2871 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2872 if ((fp
= fopen(cachefile
, "w"))) {
2874 FILE *gp
= ce
->ce_fp
;
2876 fseeko(gp
, 0, SEEK_SET
);
2878 while ((cc
= fread(buffer
, sizeof(*buffer
),
2879 sizeof(buffer
), gp
)) > 0)
2880 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2881 advise ("openURL", "fwrite");
2887 admonish(ce
->ce_file
, "error reading");
2888 (void) m_unlink (cachefile
);
2895 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2896 *file
= ce
->ce_file
;
2897 return fileno(ce
->ce_fp
);
2902 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2903 * has to be base64 decoded.
2906 readDigest (CT ct
, char *cp
)
2908 unsigned char *digest
;
2911 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2912 const size_t maxlen
= sizeof ct
->c_digest
;
2914 if (strlen ((char *) digest
) <= maxlen
) {
2915 memcpy (ct
->c_digest
, digest
, maxlen
);
2920 fprintf (stderr
, "MD5 digest=");
2921 for (i
= 0; i
< maxlen
; ++i
) {
2922 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2924 fprintf (stderr
, "\n");
2930 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2931 (int) strlen ((char *) digest
));
2941 /* Multipart parts might have content before the first subpart and/or
2942 after the last subpart that hasn't been stored anywhere else, so do
2945 get_leftover_mp_content (CT ct
, int before
/* or after */)
2947 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2949 bool found_boundary
= false;
2955 char *content
= NULL
;
2957 if (! m
) return NOTOK
;
2960 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2962 /* Isolate the beginning of this part to the beginning of the
2963 first subpart and save any content between them. */
2964 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2965 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2966 boundary
= concat ("--", m
->mp_start
, NULL
);
2968 struct part
*last_subpart
= NULL
;
2969 struct part
*subpart
;
2971 /* Go to the last subpart to get its end position. */
2972 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2973 last_subpart
= subpart
;
2976 if (last_subpart
== NULL
) return NOTOK
;
2978 /* Isolate the end of the last subpart to the end of this part
2979 and save any content between them. */
2980 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2981 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2982 boundary
= concat ("--", m
->mp_stop
, NULL
);
2985 /* Back up by 1 to pick up the newline. */
2986 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2988 /* Don't look beyond beginning of first subpart (before) or
2989 next part (after). */
2990 if (read
> max
) bufp
[read
-max
] = '\0';
2993 if (! strcmp (bufp
, boundary
)) {
2994 found_boundary
= true;
2997 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2998 found_boundary
= true;
3003 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3005 char *old_content
= content
;
3006 content
= concat (content
, bufp
, NULL
);
3010 ? concat ("\n", bufp
, NULL
)
3011 : concat (bufp
, NULL
);
3016 if (found_boundary
|| read
> max
) break;
3018 if (read
> max
) break;
3022 /* Skip the newline if that's all there is. */
3026 /* Remove trailing newline, except at EOF. */
3027 if ((before
|| ! feof (ct
->c_fp
)) &&
3028 (cp
= content
+ strlen (content
)) > content
&&
3033 if (strlen (content
) > 1) {
3035 m
->mp_content_before
= content
;
3037 m
->mp_content_after
= content
;
3052 ct_type_str (int type
)
3055 case CT_APPLICATION
:
3056 return "application";
3072 return "unknown_type";
3078 ct_subtype_str (int type
, int subtype
)
3081 case CT_APPLICATION
:
3083 case APPLICATION_OCTETS
:
3085 case APPLICATION_POSTSCRIPT
:
3086 return "postscript";
3088 return "unknown_app_subtype";
3092 case MESSAGE_RFC822
:
3094 case MESSAGE_PARTIAL
:
3096 case MESSAGE_EXTERNAL
:
3099 return "unknown_msg_subtype";
3105 case MULTI_ALTERNATE
:
3106 return "alternative";
3109 case MULTI_PARALLEL
:
3114 return "unknown_multipart_subtype";
3125 return "unknown_text_subtype";
3128 return "unknown_type";
3134 ct_str_type (const char *type
)
3136 struct str2init
*s2i
;
3138 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3139 if (! strcasecmp (type
, s2i
->si_key
)) {
3143 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3152 ct_str_subtype (int type
, const char *subtype
)
3157 case CT_APPLICATION
:
3158 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3159 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3163 return kv
->kv_value
;
3165 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3166 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3170 return kv
->kv_value
;
3172 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3173 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3177 return kv
->kv_value
;
3179 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3180 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3184 return kv
->kv_value
;
3191 /* Find the content type and InitFunc for the CT. */
3192 const struct str2init
*
3193 get_ct_init (int type
)
3195 const struct str2init
*sp
;
3197 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3198 if (type
== sp
->si_val
) {
3207 ce_str (int encoding
)
3213 return "quoted-printable";
3229 /* Find the content type and InitFunc for the content encoding method. */
3230 const struct str2init
*
3231 get_ce_method (const char *method
)
3233 struct str2init
*sp
;
3235 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3236 if (! strcasecmp (method
, sp
->si_key
)) {
3245 * Parse a series of MIME attributes (or parameters) given a header as
3248 * Arguments include:
3250 * filename - Name of input file (for error messages)
3251 * fieldname - Name of field being processed
3252 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3253 * Updated to point to end of attributes when finished.
3254 * param_head - Pointer to head of parameter list
3255 * param_tail - Pointer to tail of parameter list
3256 * commentp - Pointer to header comment pointer (may be NULL)
3258 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3259 * DONE to indicate a benign error (minor parsing error, but the program
3264 parse_header_attrs (const char *filename
, const char *fieldname
,
3265 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3268 char *cp
= *header_attrp
;
3274 struct sectlist
*next
;
3280 struct sectlist
*sechead
;
3281 struct parmlist
*next
;
3282 } *pp
, *pp2
, *phead
= NULL
;
3284 while (*cp
== ';') {
3285 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3286 bool encoded
= false;
3287 bool partial
= false;
3288 int len
= 0, index
= 0;
3291 while (isspace ((unsigned char) *cp
))
3295 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3300 if (! suppress_extraneous_trailing_semicolon_warning
) {
3301 inform("extraneous trailing ';' in message %s's %s: "
3302 "parameter list", filename
, fieldname
);
3307 /* down case the attribute name */
3308 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3309 *dp
= tolower ((unsigned char) *dp
);
3311 for (up
= dp
; isspace ((unsigned char) *dp
);)
3313 if (dp
== cp
|| *dp
!= '=') {
3314 inform("invalid parameter in message %s's %s: field\n"
3315 " parameter %s (error detected at offset %ld)",
3316 filename
, fieldname
, cp
, (long)(dp
- cp
));
3321 * To handle RFC 2231, we have to deal with the following extensions:
3323 * name*=encoded-value
3324 * name*<N>=part-N-of-a-parameter-value
3325 * name*<N>*=encoded-part-N-of-a-parameter-value
3328 * If there's a * right before the equal sign, it's encoded.
3329 * If there's a * and one or more digits, then it's section N.
3331 * Remember we can have one or the other, or both. cp points to
3332 * beginning of name, up points past the last character in the
3336 for (vp
= cp
; vp
< up
; vp
++) {
3337 if (*vp
== '*' && vp
< up
- 1) {
3341 if (*vp
== '*' && vp
== up
- 1) {
3343 } else if (partial
) {
3344 if (isdigit((unsigned char) *vp
))
3345 index
= *vp
- '0' + index
* 10;
3347 inform("invalid parameter index in message %s's %s: field"
3348 "\n (parameter %s)", filename
, fieldname
, cp
);
3357 * Break out the parameter name and value sections and allocate
3361 nameptr
= mh_xmalloc(len
+ 1);
3362 strncpy(nameptr
, cp
, len
);
3363 nameptr
[len
] = '\0';
3365 for (dp
++; isspace ((unsigned char) *dp
);)
3370 * Single quotes delimit the character set and language tag.
3371 * They are required on the first section (or a complete
3376 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3382 charset
= mh_xmalloc(len
+ 1);
3383 strncpy(charset
, dp
, len
);
3384 charset
[len
] = '\0';
3390 inform("missing charset in message %s's %s: field\n"
3391 " (parameter %s)", filename
, fieldname
, nameptr
);
3397 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3404 lang
= mh_xmalloc(len
+ 1);
3405 strncpy(lang
, dp
, len
);
3412 inform("missing language tag in message %s's %s: field\n"
3413 " (parameter %s)", filename
, fieldname
, nameptr
);
3423 * At this point vp should be pointing at the beginning
3424 * of the encoded value/section. Continue until we reach
3425 * the end or get whitespace. But first, calculate the
3426 * length so we can allocate the correct buffer size.
3429 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3431 if (*(vp
+ 1) == '\0' ||
3432 !isxdigit((unsigned char) *(vp
+ 1)) ||
3433 *(vp
+ 2) == '\0' ||
3434 !isxdigit((unsigned char) *(vp
+ 2))) {
3435 inform("invalid encoded sequence in message %s's %s: field\n"
3436 " (parameter %s)", filename
, fieldname
, nameptr
);
3447 up
= valptr
= mh_xmalloc(len
+ 1);
3449 for (vp
= dp
; istoken(*vp
); vp
++) {
3451 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3462 * A "normal" string. If it's got a leading quote, then we
3463 * strip the quotes out. Otherwise go until we reach the end
3464 * or get whitespace. Note we scan it twice; once to get the
3465 * length, then the second time copies it into the destination
3472 for (cp
= dp
+ 1;;) {
3476 inform("invalid quoted-string in message %s's %s: field\n"
3477 " (parameter %s)", filename
, fieldname
, nameptr
);
3497 for (cp
= dp
; istoken (*cp
); cp
++) {
3502 valptr
= mh_xmalloc(len
+ 1);
3506 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3514 strncpy(valptr
, cp
= dp
, len
);
3522 * If 'partial' is set, we don't allocate a parameter now. We
3523 * put it on the parameter linked list to be reassembled later.
3525 * "phead" points to a list of all parameters we need to reassemble.
3526 * Each parameter has a list of sections. We insert the sections in
3531 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3532 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3547 * Insert this into the section linked list
3555 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3556 sp
->next
= pp
->sechead
;
3559 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3560 if (sp2
->index
== sp
->index
) {
3561 inform("duplicate index (%d) in message %s's %s: field"
3562 "\n (parameter %s)", sp
->index
, filename
,
3563 fieldname
, nameptr
);
3566 if (sp2
->index
< sp
->index
&&
3567 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3568 sp
->next
= sp2
->next
;
3575 inform("Internal error: cannot insert partial param "
3576 "in message %s's %s: field\n (parameter %s)",
3577 filename
, fieldname
, nameptr
);
3583 * Save our charset and lang tags.
3586 if (index
== 0 && encoded
) {
3588 pp
->charset
= charset
;
3593 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3594 pm
->pm_charset
= charset
;
3598 while (isspace ((unsigned char) *cp
))
3602 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3608 * Now that we're done, reassemble all of the partial parameters.
3611 for (pp
= phead
; pp
!= NULL
; ) {
3615 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3616 if (sp
->index
!= pindex
++) {
3617 inform("missing section %d for parameter in message "
3618 "%s's %s: field\n (parameter %s)", pindex
- 1,
3619 filename
, fieldname
, pp
->name
);
3625 p
= q
= mh_xmalloc(tlen
+ 1);
3626 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3627 memcpy(q
, sp
->value
, sp
->len
);
3637 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3638 pm
->pm_charset
= pp
->charset
;
3639 pm
->pm_lang
= pp
->lang
;
3650 * Return the charset for a particular content type.
3654 content_charset (CT ct
)
3656 char *ret_charset
= NULL
;
3658 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3660 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3665 * Create a string based on a list of output parameters. Assume that this
3666 * parameter string will be appended to an existing header, so start out
3667 * with the separator (;). Perform RFC 2231 encoding when necessary.
3671 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3673 char *paramout
= NULL
;
3674 char line
[CPERLIN
* 2], *q
;
3675 int curlen
, index
, cont
, encode
, i
;
3676 size_t valoff
, numchars
;
3678 while (params
!= NULL
) {
3684 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3687 if (strlen(params
->pm_name
) > CPERLIN
) {
3688 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3693 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3696 * Loop until we get a parameter that fits within a line. We
3697 * assume new lines start with a tab, so check our overflow based
3707 * At this point we're definitely continuing the line, so
3708 * be sure to include the parameter name and section index.
3711 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3712 params
->pm_name
, index
);
3715 * Both of these functions do a NUL termination
3719 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3720 numchars
, valoff
, index
);
3722 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3732 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3737 * "line" starts with a ;\n\t, so that doesn't count against
3738 * the length. But add 8 since it starts with a tab; that's
3739 * how we end up with 5.
3742 initialwidth
= strlen(line
) + 5;
3745 * At this point the line should be built, so add it to our
3746 * current output buffer.
3749 paramout
= add(line
, paramout
);
3753 * If this won't fit on the line, start a new one. Save room in
3754 * case we need a semicolon on the end
3757 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3769 * At this point, we're either finishing a continued parameter, or
3770 * we're working on a new one.
3774 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3775 params
->pm_name
, index
);
3777 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3782 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3783 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3785 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3786 strlen(params
->pm_value
+ valoff
), valoff
);
3793 paramout
= add(line
, paramout
);
3794 initialwidth
+= strlen(line
);
3796 params
= params
->pm_next
;
3800 *offsetout
= initialwidth
;
3806 * Calculate the size of a parameter.
3810 * pm - The parameter being output
3811 * index - If continuing the parameter, the index of the section
3813 * valueoff - The current offset into the parameter value that we're
3814 * working on (previous sections have consumed valueoff bytes).
3815 * encode - Set if we should perform encoding on this parameter section
3816 * (given that we're consuming bytesfit bytes).
3817 * cont - Set if the remaining data in value will not fit on a single
3818 * line and will need to be continued.
3819 * bytesfit - The number of bytes that we can consume from the parameter
3820 * value and still fit on a completely new line. The
3821 * calculation assumes the new line starts with a tab,
3822 * includes the parameter name and any encoding, and fits
3823 * within CPERLIN bytes. Will always be at least 1.
3827 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3830 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3831 size_t len
= 0, fit
= 0;
3832 int fitlimit
= 0, eightbit
, maxfit
;
3837 * Add up the length. First, start with the parameter name.
3840 len
= strlen(pm
->pm_name
);
3843 * Scan the parameter value and see if we need to do encoding for this
3847 eightbit
= contains8bit(start
, NULL
);
3850 * Determine if we need to encode this section. Encoding is necessary if:
3852 * - There are any 8-bit characters at all and we're on the first
3854 * - There are 8-bit characters within N bytes of our section start.
3855 * N is calculated based on the number of bytes it would take to
3856 * reach CPERLIN. Specifically:
3857 * 8 (starting tab) +
3858 * strlen(param name) +
3859 * 4 ('* for section marker, '=', opening/closing '"')
3861 * is the number of bytes used by everything that isn't part of the
3862 * value. So that gets subtracted from CPERLIN.
3865 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3866 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3867 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3871 len
++; /* Add in equal sign */
3875 * We're using maxfit as a marker for how many characters we can
3876 * fit into the line. Bump it by two because we're not using quotes
3883 * If we don't have a charset or language tag in this parameter,
3887 if (! pm
->pm_charset
) {
3888 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3889 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3890 die("8-bit characters in parameter \"%s\", but "
3891 "local character set is US-ASCII", pm
->pm_name
);
3894 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3896 len
++; /* For the encoding marker */
3899 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3904 * We know we definitely need to include an index. maxfit already
3905 * includes the section marker.
3907 len
+= strlen(indexchar
);
3909 for (p
= start
; *p
!= '\0'; p
++) {
3910 if (isparamencode(*p
)) {
3918 * Just so there's no confusion: maxfit is counting OUTPUT
3919 * characters (post-encoding). fit is counting INPUT characters.
3921 if (! fitlimit
&& maxfit
>= 0)
3923 else if (! fitlimit
)
3928 * Calculate the string length, but add room for quoting \
3929 * and " if necessary. Also account for quotes at beginning
3932 for (p
= start
; *p
!= '\0'; p
++) {
3943 if (! fitlimit
&& maxfit
>= 0)
3945 else if (! fitlimit
)
3962 * Output an encoded parameter string.
3966 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3967 size_t valueoff
, int index
)
3969 size_t outlen
= 0, n
;
3970 char *endptr
= output
+ len
, *p
;
3973 * First, output the marker for an encoded string.
3981 * If the index is 0, output the character set and language tag.
3982 * If theses were NULL, they should have already been filled in
3987 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3991 if (output
> endptr
) {
3992 inform("Internal error: parameter buffer overflow");
3998 * Copy over the value, encoding if necessary
4001 p
= pm
->pm_value
+ valueoff
;
4002 while (valuelen
-- > 0) {
4003 if (isparamencode(*p
)) {
4004 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4011 if (output
> endptr
) {
4012 inform("Internal error: parameter buffer overflow");
4023 * Output a "normal" parameter, without encoding. Be sure to escape
4024 * quotes and backslashes if necessary.
4028 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4032 char *endptr
= output
+ len
, *p
;
4038 p
= pm
->pm_value
+ valueoff
;
4040 while (valuelen
-- > 0) {
4051 if (output
> endptr
) {
4052 inform("Internal error: parameter buffer overflow");
4057 if (output
- 2 > endptr
) {
4058 inform("Internal error: parameter buffer overflow");
4069 * Add a parameter to the parameter linked list
4073 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4078 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4079 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4082 (*last
)->pm_next
= pm
;
4093 * Either replace a current parameter with a new value, or add the parameter
4094 * to the parameter linked list.
4098 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4102 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4103 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4105 * If nocopy is set, it's assumed that we own both name
4106 * and value. We don't need name, so we discard it now.
4111 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4116 return add_param(first
, last
, name
, value
, nocopy
);
4120 * Retrieve a parameter value from a parameter linked list. If the parameter
4121 * value needs converted to the local character set, do that now.
4125 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4127 while (first
!= NULL
) {
4128 if (strcasecmp(name
, first
->pm_name
) == 0) {
4130 return first
->pm_value
;
4131 return getcpy(get_param_value(first
, replace
));
4133 first
= first
->pm_next
;
4140 * Return a parameter value, converting to the local character set if
4145 get_param_value(PM pm
, char replace
)
4147 static char buffer
[4096]; /* I hope no parameters are larger */
4148 size_t bufsize
= sizeof(buffer
);
4153 ICONV_CONST
char *p
;
4154 #else /* HAVE_ICONV */
4156 #endif /* HAVE_ICONV */
4161 * If we don't have a character set indicated, it's assumed to be
4162 * US-ASCII. If it matches our character set, we don't need to convert
4166 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4167 strlen(pm
->pm_charset
))) {
4168 return pm
->pm_value
;
4172 * In this case, we need to convert. If we have iconv support, use
4173 * that. Otherwise, go through and simply replace every non-ASCII
4174 * character with the substitution character.
4179 bufsize
= sizeof(buffer
);
4180 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4182 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4183 if (cd
== (iconv_t
) -1) {
4187 inbytes
= strlen(pm
->pm_value
);
4191 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4192 if (errno
!= EILSEQ
) {
4197 * Reset shift state, substitute our character,
4198 * try to restart conversion.
4201 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4214 for (++p
, --inbytes
;
4215 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4234 #endif /* HAVE_ICONV */
4237 * Take everything non-ASCII and substitute the replacement character
4241 bufsize
= sizeof(buffer
);
4242 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4243 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))