1 /* mhparse.c -- routines to parse the contents of MIME messages
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
14 #include <h/mhparse.h>
16 #include <h/mhcachesbr.h>
17 #include "../sbr/m_mktemp.h"
21 #endif /* HAVE_ICONV */
26 int checksw
= 0; /* check Content-MD5 field */
29 * These are for mhfixmsg to:
30 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
32 * 2) Suppress the warning about bogus multipart content, and report it.
33 * 3) Suppress the warning about extraneous trailing ';' in header parameter
36 int skip_mp_cte_check
;
37 int suppress_bogus_mp_content_warning
;
39 int suppress_extraneous_trailing_semicolon_warning
;
42 * By default, suppress warning about multiple MIME-Version header fields.
44 int suppress_multiple_mime_version_warning
= 1;
46 /* list of preferred type/subtype pairs, for -prefer */
47 char *preferred_types
[NPREFS
],
48 *preferred_subtypes
[NPREFS
];
53 * Structures for TEXT messages
55 struct k2v SubText
[] = {
56 { "plain", TEXT_PLAIN
},
57 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
58 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
59 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
62 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
65 * Structures for MULTIPART messages
67 struct k2v SubMultiPart
[] = {
68 { "mixed", MULTI_MIXED
},
69 { "alternative", MULTI_ALTERNATE
},
70 { "digest", MULTI_DIGEST
},
71 { "parallel", MULTI_PARALLEL
},
72 { "related", MULTI_RELATED
},
73 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
77 * Structures for MESSAGE messages
79 struct k2v SubMessage
[] = {
80 { "rfc822", MESSAGE_RFC822
},
81 { "partial", MESSAGE_PARTIAL
},
82 { "external-body", MESSAGE_EXTERNAL
},
83 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
87 * Structure for APPLICATION messages
89 struct k2v SubApplication
[] = {
90 { "octet-stream", APPLICATION_OCTETS
},
91 { "postscript", APPLICATION_POSTSCRIPT
},
92 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
96 * Mapping of names of CTE types in mhbuild directives
98 static struct k2v EncodingType
[] = {
101 { "q-p", CE_QUOTED
},
102 { "quoted-printable", CE_QUOTED
},
103 { "b64", CE_BASE64
},
104 { "base64", CE_BASE64
},
111 int type_ok (CT
, int);
112 void content_error (char *, CT
, char *, ...);
117 static CT
get_content (FILE *, char *, int);
118 static int get_comment (const char *, const char *, char **, char **);
120 static int InitGeneric (CT
);
121 static int InitText (CT
);
122 static int InitMultiPart (CT
);
123 static void reverse_parts (CT
);
124 static void prefer_parts(CT ct
);
125 static int InitMessage (CT
);
126 static int InitApplication (CT
);
127 static int init_encoding (CT
, OpenCEFunc
);
128 static unsigned long size_encoding (CT
);
129 static int InitBase64 (CT
);
130 static int openBase64 (CT
, char **);
131 static int InitQuoted (CT
);
132 static int openQuoted (CT
, char **);
133 static int Init7Bit (CT
);
134 static int openExternal (CT
, CT
, CE
, char **, int *);
135 static int InitFile (CT
);
136 static int openFile (CT
, char **);
137 static int InitFTP (CT
);
138 static int openFTP (CT
, char **);
139 static int InitMail (CT
);
140 static int openMail (CT
, char **);
141 static int readDigest (CT
, char *);
142 static int get_leftover_mp_content (CT
, int);
143 static int InitURL (CT
);
144 static int openURL (CT
, char **);
145 static int parse_header_attrs (const char *, const char *, char **, PM
*,
147 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
148 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
149 static int get_dispo (char *, CT
, int);
151 struct str2init str2cts
[] = {
152 { "application", CT_APPLICATION
, InitApplication
},
153 { "audio", CT_AUDIO
, InitGeneric
},
154 { "image", CT_IMAGE
, InitGeneric
},
155 { "message", CT_MESSAGE
, InitMessage
},
156 { "multipart", CT_MULTIPART
, InitMultiPart
},
157 { "text", CT_TEXT
, InitText
},
158 { "video", CT_VIDEO
, InitGeneric
},
159 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
160 { NULL
, CT_UNKNOWN
, NULL
},
163 struct str2init str2ces
[] = {
164 { "base64", CE_BASE64
, InitBase64
},
165 { "quoted-printable", CE_QUOTED
, InitQuoted
},
166 { "8bit", CE_8BIT
, Init7Bit
},
167 { "7bit", CE_7BIT
, Init7Bit
},
168 { "binary", CE_BINARY
, Init7Bit
},
169 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
170 { NULL
, CE_UNKNOWN
, NULL
},
174 * NOTE WELL: si_key MUST NOT have value of NOTOK
176 * si_key is 1 if access method is anonymous.
178 struct str2init str2methods
[] = {
179 { "afs", 1, InitFile
},
180 { "anon-ftp", 1, InitFTP
},
181 { "ftp", 0, InitFTP
},
182 { "local-file", 0, InitFile
},
183 { "mail-server", 0, InitMail
},
184 { "url", 0, InitURL
},
190 * Main entry point for parsing a MIME message or file.
191 * It returns the Content structure for the top level
192 * entity in the file.
196 parse_mime (char *file
)
205 bogus_mp_content
= 0;
208 * Check if file is actually standard input
210 if ((is_stdin
= !(strcmp (file
, "-")))) {
211 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
213 advise("mhparse", "unable to create temporary file in %s",
217 file
= mh_xstrdup(tfile
);
219 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
220 if (fwrite(buffer
, 1, n
, fp
) != n
) {
221 (void) m_unlink (file
);
222 advise (file
, "error copying to temporary file");
228 if (ferror (stdin
)) {
229 (void) m_unlink (file
);
230 advise ("stdin", "error reading");
234 (void) m_unlink (file
);
235 advise (file
, "error writing");
238 fseek (fp
, 0L, SEEK_SET
);
239 } else if (stat (file
, &statbuf
) == NOTOK
) {
240 advise (file
, "unable to stat");
242 } else if (S_ISDIR(statbuf
.st_mode
)) {
243 /* Don't try to parse a directory. */
244 inform("%s is a directory", file
);
246 } else if ((fp
= fopen (file
, "r")) == NULL
) {
247 advise (file
, "unable to read");
251 if (!(ct
= get_content (fp
, file
, 1))) {
253 (void) m_unlink (file
);
254 inform("unable to decode %s", file
);
259 ct
->c_unlink
= 1; /* temp file to remove */
263 if (ct
->c_end
== 0L) {
264 fseek (fp
, 0L, SEEK_END
);
265 ct
->c_end
= ftell (fp
);
268 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
280 * Main routine for reading/parsing the headers
281 * of a message content.
283 * toplevel = 1 # we are at the top level of the message
284 * toplevel = 0 # we are inside message type or multipart type
285 * # other than multipart/digest
286 * toplevel = -1 # we are inside multipart/digest
287 * NB: on failure we will fclose(in)!
291 get_content (FILE *in
, char *file
, int toplevel
)
294 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
298 m_getfld_state_t gstate
;
300 /* allocate the content structure */
303 ct
->c_file
= add (file
, NULL
);
304 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
307 * Parse the header fields for this
308 * content into a linked list.
310 gstate
= m_getfld_state_init(in
);
311 m_getfld_track_filepos2(&gstate
);
312 for (compnum
= 1;;) {
313 int bufsz
= sizeof buf
;
314 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
319 /* get copies of the buffers */
320 np
= mh_xstrdup(name
);
321 vp
= mh_xstrdup(buf
);
323 /* if necessary, get rest of field */
324 while (state
== FLDPLUS
) {
326 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
327 vp
= add (buf
, vp
); /* add to previous value */
330 /* Now add the header data to the list */
331 add_header (ct
, np
, vp
);
333 /* continue, to see if this isn't the last header field */
334 ct
->c_begin
= ftell (in
) + 1;
338 /* There are two cases. The unusual one is when there is no
339 * blank line between the headers and the body. This is
340 * indicated by the name of the header starting with `:'.
342 * For both cases, normal first, `1' is the desired c_begin
343 * file position for the start of the body, and `2' is the
344 * file position when buf is returned.
346 * f o o : b a r \n \n b o d y \n bufsz = 6
348 * f o o : b a r \n b o d y \n bufsz = 4
351 * For the normal case, bufsz includes the
352 * header-terminating `\n', even though it is not in buf,
353 * but bufsz isn't affected when it's missing in the unusual
355 if (name
[0] == ':') {
356 ct
->c_begin
= ftell(in
) - bufsz
;
358 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
363 ct
->c_begin
= ftell (in
);
368 adios (NULL
, "message format error in component #%d", compnum
);
371 adios (NULL
, "getfld() returned %d", state
);
374 /* break out of the loop */
377 m_getfld_state_destroy (&gstate
);
380 * Read the content headers. We will parse the
381 * MIME related header fields into their various
382 * structures and set internal flags related to
383 * content type/subtype, etc.
386 hp
= ct
->c_first_hf
; /* start at first header field */
388 /* Get MIME-Version field */
389 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
394 vrsn
= add (hp
->value
, NULL
);
396 /* Now, cleanup this field */
399 while (isspace ((unsigned char) *cp
))
401 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
403 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
404 if (!isspace ((unsigned char) *dp
))
408 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
411 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
414 for (dp
= cp
; istoken (*dp
); dp
++)
418 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
421 inform("message %s has unknown value for %s: field (%s), continuing...",
422 ct
->c_file
, VRSN_FIELD
, cp
);
427 if (! suppress_multiple_mime_version_warning
)
428 inform("message %s has multiple %s: fields",
429 ct
->c_file
, VRSN_FIELD
);
433 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
434 /* Get Content-Type field */
435 struct str2init
*s2i
;
436 CI ci
= &ct
->c_ctinfo
;
438 /* Check if we've already seen a Content-Type header */
440 inform("message %s has multiple %s: fields",
441 ct
->c_file
, TYPE_FIELD
);
445 /* Parse the Content-Type field */
446 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
450 * Set the Init function and the internal
451 * flag for this content type.
453 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
454 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
456 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
458 ct
->c_type
= s2i
->si_val
;
459 ct
->c_ctinitfnx
= s2i
->si_init
;
461 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
462 /* Get Content-Transfer-Encoding field */
464 struct str2init
*s2i
;
467 * Check if we've already seen the
468 * Content-Transfer-Encoding field
471 inform("message %s has multiple %s: fields",
472 ct
->c_file
, ENCODING_FIELD
);
476 /* get copy of this field */
477 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
479 while (isspace ((unsigned char) *cp
))
481 for (dp
= cp
; istoken (*dp
); dp
++)
487 * Find the internal flag and Init function
488 * for this transfer encoding.
490 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
491 if (!strcasecmp (cp
, s2i
->si_key
))
493 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
496 ct
->c_encoding
= s2i
->si_val
;
498 /* Call the Init function for this encoding */
499 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
502 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
503 /* Get Content-MD5 field */
509 if (ct
->c_digested
) {
510 inform("message %s has multiple %s: fields",
511 ct
->c_file
, MD5_FIELD
);
515 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
517 while (isspace ((unsigned char) *cp
))
519 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
521 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
522 if (!isspace ((unsigned char) *dp
))
526 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
529 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
534 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
542 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
543 /* Get Content-ID field */
544 ct
->c_id
= add (hp
->value
, ct
->c_id
);
546 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
547 /* Get Content-Description field */
548 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
550 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
551 /* Get Content-Disposition field */
552 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
557 hp
= hp
->next
; /* next header field */
561 * Check if we saw a Content-Type field.
562 * If not, then assign a default value for
563 * it, and the Init function.
567 * If we are inside a multipart/digest message,
568 * so default type is message/rfc822
571 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
573 ct
->c_type
= CT_MESSAGE
;
574 ct
->c_ctinitfnx
= InitMessage
;
577 * Else default type is text/plain
579 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
581 ct
->c_type
= CT_TEXT
;
582 ct
->c_ctinitfnx
= InitText
;
586 /* Use default Transfer-Encoding, if necessary */
588 ct
->c_encoding
= CE_7BIT
;
601 * small routine to add header field to list
605 add_header (CT ct
, char *name
, char *value
)
609 /* allocate header field structure */
612 /* link data into header structure */
617 /* link header structure into the list */
618 if (ct
->c_first_hf
== NULL
) {
619 ct
->c_first_hf
= hp
; /* this is the first */
622 ct
->c_last_hf
->next
= hp
; /* add it to the end */
631 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
632 * directives. Fills in the information of the CTinfo structure.
635 get_ctinfo (char *cp
, CT ct
, int magic
)
644 /* store copy of Content-Type line */
645 cp
= ct
->c_ctline
= add (cp
, NULL
);
647 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
650 /* change newlines to spaces */
651 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
654 /* trim trailing spaces */
655 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
656 if (!isspace ((unsigned char) *dp
))
661 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
663 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
664 &ci
->ci_comment
) == NOTOK
)
667 for (dp
= cp
; istoken (*dp
); dp
++)
671 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
676 inform("invalid %s: field in message %s (empty type)",
677 TYPE_FIELD
, ct
->c_file
);
680 to_lower(ci
->ci_type
);
682 while (isspace ((unsigned char) *cp
))
685 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
686 &ci
->ci_comment
) == NOTOK
)
691 ci
->ci_subtype
= mh_xstrdup("");
696 while (isspace ((unsigned char) *cp
))
699 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
700 &ci
->ci_comment
) == NOTOK
)
703 for (dp
= cp
; istoken (*dp
); dp
++)
707 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
711 if (!*ci
->ci_subtype
) {
712 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
713 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
716 to_lower(ci
->ci_subtype
);
719 while (isspace ((unsigned char) *cp
))
722 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
723 &ci
->ci_comment
) == NOTOK
)
726 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
727 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
728 &ci
->ci_comment
)) != OK
) {
729 return status
== NOTOK
? NOTOK
: OK
;
733 * Get any <Content-Id> given in buffer
735 if (magic
&& *cp
== '<') {
738 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
739 inform("invalid ID in message %s", ct
->c_file
);
745 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
751 while (isspace ((unsigned char) *cp
))
756 * Get any [Content-Description] given in buffer.
758 if (magic
&& *cp
== '[') {
760 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
764 inform("invalid description in message %s", ct
->c_file
);
772 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
778 while (isspace ((unsigned char) *cp
))
783 * Get any {Content-Disposition} given in buffer.
785 if (magic
&& *cp
== '{') {
787 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
791 inform("invalid disposition in message %s", ct
->c_file
);
799 if (get_dispo(cp
, ct
, 1) != OK
)
805 while (isspace ((unsigned char) *cp
))
810 * Get any extension directives (right now just the content transfer
811 * encoding, but maybe others) that we care about.
814 if (magic
&& *cp
== '*') {
816 * See if it's a CTE we match on
821 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
825 inform("invalid null transfer encoding specification");
832 ct
->c_reqencoding
= CE_UNKNOWN
;
834 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
835 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
836 ct
->c_reqencoding
= kv
->kv_value
;
841 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
842 inform("invalid CTE specification: \"%s\"", dp
);
846 while (isspace ((unsigned char) *cp
))
851 * Check if anything is left over
855 ci
->ci_magic
= mh_xstrdup(cp
);
857 /* If there is a Content-Disposition header and it doesn't
858 have a *filename=, extract it from the magic contents.
859 The r1bindex call skips any leading directory
861 if (ct
->c_dispo_type
&&
862 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
863 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
864 r1bindex(ci
->ci_magic
, '/'), 0);
868 inform("extraneous information in message %s's %s: field\n"
869 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
877 * Parse out a Content-Disposition header. A lot of this is cribbed from
881 get_dispo (char *cp
, CT ct
, int buildflag
)
883 char *dp
, *dispoheader
;
888 * Save the whole copy of the Content-Disposition header, unless we're
889 * processing a mhbuild directive. A NULL c_dispo will be a flag to
890 * mhbuild that the disposition header needs to be generated at that
894 dispoheader
= cp
= add(cp
, NULL
);
896 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
899 /* change newlines to spaces */
900 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
903 /* trim trailing spaces */
904 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
905 if (!isspace ((unsigned char) *dp
))
910 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
912 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
918 for (dp
= cp
; istoken (*dp
); dp
++)
922 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
926 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
929 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
930 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
932 if (status
== NOTOK
) {
937 inform("extraneous information in message %s's %s: field\n (%s)",
938 ct
->c_file
, DISPO_FIELD
, cp
);
944 ct
->c_dispo
= dispoheader
;
951 get_comment (const char *filename
, const char *fieldname
, char **ap
,
956 char c
, buffer
[BUFSIZ
], *dp
;
966 inform("invalid comment in message %s's %s: field",
967 filename
, fieldname
);
972 if ((c
= *cp
++) == '\0')
995 if ((dp
= *commentp
)) {
996 *commentp
= concat (dp
, " ", buffer
, NULL
);
999 *commentp
= mh_xstrdup(buffer
);
1003 while (isspace ((unsigned char) *cp
))
1014 * Handles content types audio, image, and video.
1015 * There's not much to do right here.
1023 return OK
; /* not much to do here */
1034 char buffer
[BUFSIZ
];
1039 CI ci
= &ct
->c_ctinfo
;
1041 /* check for missing subtype */
1042 if (!*ci
->ci_subtype
)
1043 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1046 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1048 /* allocate text character set structure */
1050 ct
->c_ctparams
= (void *) t
;
1052 /* scan for charset parameter */
1053 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1054 if (!strcasecmp (pm
->pm_name
, "charset"))
1057 /* check if content specified a character set */
1059 chset
= pm
->pm_value
;
1060 t
->tx_charset
= CHARSET_SPECIFIED
;
1062 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1066 * If we can not handle character set natively,
1067 * then check profile for string to modify the
1068 * terminal or display method.
1070 * termproc is for mhshow, though mhlist -debug prints it, too.
1072 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1073 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1074 if ((cp
= context_find (buffer
)))
1075 ct
->c_termproc
= mh_xstrdup(cp
);
1087 InitMultiPart (CT ct
)
1097 struct multipart
*m
;
1098 struct part
*part
, **next
;
1099 CI ci
= &ct
->c_ctinfo
;
1104 * The encoding for multipart messages must be either
1105 * 7bit, 8bit, or binary (per RFC 2045).
1107 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1108 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1109 /* Copy the Content-Transfer-Encoding header field body so we can
1110 remove any trailing whitespace and leading blanks from it. */
1111 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1113 bp
= cte
+ strlen (cte
) - 1;
1114 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1115 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1117 inform("\"%s/%s\" type in message %s must be encoded in\n"
1118 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1119 "mhfixmsg -fixcte can fix it, or\n"
1120 "manually edit the file and change the \"%s\"\n"
1121 "Content-Transfer-Encoding to one of those. For now, continuing...",
1122 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1129 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1132 * Check for "boundary" parameter, which is
1133 * required for multipart messages.
1136 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1137 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1143 /* complain if boundary parameter is missing */
1145 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1146 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1150 /* allocate primary structure for multipart info */
1152 ct
->c_ctparams
= (void *) m
;
1154 /* check if boundary parameter contains only whitespace characters */
1155 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1158 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1159 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1163 /* remove trailing whitespace from boundary parameter */
1164 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1165 if (!isspace ((unsigned char) *dp
))
1169 /* record boundary separators */
1170 m
->mp_start
= concat (bp
, "\n", NULL
);
1171 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1173 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1174 advise (ct
->c_file
, "unable to open for reading");
1178 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1180 next
= &m
->mp_parts
;
1184 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1189 if (bufp
[0] != '-' || bufp
[1] != '-')
1192 if (strcmp (bufp
+ 2, m
->mp_start
))
1197 next
= &part
->mp_next
;
1199 if (!(p
= get_content (fp
, ct
->c_file
,
1200 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1208 fseek (fp
, pos
, SEEK_SET
);
1211 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1215 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1216 if (p
->c_end
< p
->c_begin
)
1217 p
->c_begin
= p
->c_end
;
1222 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1227 if (! suppress_bogus_mp_content_warning
) {
1228 inform("bogus multipart content in message %s", ct
->c_file
);
1230 bogus_mp_content
= 1;
1232 if (!inout
&& part
) {
1234 p
->c_end
= ct
->c_end
;
1236 if (p
->c_begin
>= p
->c_end
) {
1237 for (next
= &m
->mp_parts
; *next
!= part
;
1238 next
= &((*next
)->mp_next
))
1247 /* reverse the order of the parts for multipart/alternative */
1248 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1254 * label all subparts with part number, and
1255 * then initialize the content of the subpart.
1260 char partnam
[BUFSIZ
];
1263 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1264 pp
= partnam
+ strlen (partnam
);
1269 for (part
= m
->mp_parts
, partnum
= 1; part
;
1270 part
= part
->mp_next
, partnum
++) {
1273 sprintf (pp
, "%d", partnum
);
1274 p
->c_partno
= mh_xstrdup(partnam
);
1276 /* initialize the content of the subparts */
1277 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1286 get_leftover_mp_content (ct
, 1);
1287 get_leftover_mp_content (ct
, 0);
1297 * reverse the order of the parts of a multipart/alternative,
1298 * presumably to put the "most favored" alternative first, for
1299 * ease of choosing/displaying it later on. from a mail message on
1300 * nmh-workers, from kenh:
1301 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1302 * see code in mhn that did the same thing... According to the RCS
1303 * logs, that code was around from the initial checkin of mhn.c by
1304 * John Romine in 1992, which is as far back as we have."
1307 reverse_parts (CT ct
)
1309 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1313 /* Reverse the order of its parts by walking the mp_parts list
1314 and pushing each node to the front. */
1315 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1316 next
= part
->mp_next
;
1317 part
->mp_next
= m
->mp_parts
;
1323 move_preferred_part (CT ct
, char *type
, char *subtype
)
1325 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1326 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1330 /* move the matching part(s) to the head of the list: walk the
1331 * list of parts, move matching parts to a new list (maintaining
1332 * their order), and finally, concatenate the old list onto the
1339 head
->mp_next
= m
->mp_parts
;
1340 nhead
->mp_next
= NULL
;
1344 part
= head
->mp_next
;
1345 while (part
!= NULL
) {
1346 ci
= &part
->mp_part
->c_ctinfo
;
1347 if (!strcasecmp(ci
->ci_type
, type
) &&
1348 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1349 prev
->mp_next
= part
->mp_next
;
1350 part
->mp_next
= NULL
;
1351 ntail
->mp_next
= part
;
1353 part
= prev
->mp_next
;
1356 part
= prev
->mp_next
;
1359 ntail
->mp_next
= head
->mp_next
;
1360 m
->mp_parts
= nhead
->mp_next
;
1365 * move parts that match the user's preferences (-prefer) to the head
1366 * of the line. process preferences in reverse so first one given
1367 * ends up first in line
1373 for (i
= npreferred
-1; i
>= 0; i
--)
1374 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1379 /* parse_mime() arranges alternates in reverse (priority) order. This
1380 function can be used to reverse them back. This will put, for
1381 example, a text/plain part before a text/html part in a
1382 multipart/alternative part, for example, where it belongs. */
1384 reverse_alternative_parts (CT ct
) {
1385 if (ct
->c_type
== CT_MULTIPART
) {
1386 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1389 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1393 /* And call recursively on each part of a multipart. */
1394 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1395 reverse_alternative_parts (part
->mp_part
);
1408 CI ci
= &ct
->c_ctinfo
;
1410 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1411 inform("\"%s/%s\" type in message %s should be encoded in "
1412 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1417 /* check for missing subtype */
1418 if (!*ci
->ci_subtype
)
1419 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1422 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1424 switch (ct
->c_subtype
) {
1425 case MESSAGE_RFC822
:
1428 case MESSAGE_PARTIAL
:
1434 ct
->c_ctparams
= (void *) p
;
1436 /* scan for parameters "id", "number", and "total" */
1437 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1438 if (!strcasecmp (pm
->pm_name
, "id")) {
1439 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1442 if (!strcasecmp (pm
->pm_name
, "number")) {
1443 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1444 || p
->pm_partno
< 1) {
1446 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1447 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1448 ct
->c_file
, TYPE_FIELD
);
1453 if (!strcasecmp (pm
->pm_name
, "total")) {
1454 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1463 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1464 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1465 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1471 case MESSAGE_EXTERNAL
:
1479 ct
->c_ctparams
= (void *) e
;
1482 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1483 advise (ct
->c_file
, "unable to open for reading");
1487 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1489 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1497 p
->c_ceopenfnx
= NULL
;
1498 if ((exresult
= params_external (ct
, 0)) != NOTOK
1499 && p
->c_ceopenfnx
== openMail
) {
1503 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1505 content_error (NULL
, ct
,
1506 "empty body for access-type=mail-server");
1510 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1511 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1513 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1515 adios ("failed", "fread");
1518 adios (NULL
, "unexpected EOF from fread");
1521 bp
+= cc
, size
-= cc
;
1528 p
->c_end
= p
->c_begin
;
1533 if (exresult
== NOTOK
)
1535 if (e
->eb_flags
== NOTOK
)
1538 switch (p
->c_type
) {
1543 if (p
->c_subtype
!= MESSAGE_RFC822
)
1547 e
->eb_partno
= ct
->c_partno
;
1549 (*p
->c_ctinitfnx
) (p
);
1564 params_external (CT ct
, int composing
)
1567 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1568 CI ci
= &ct
->c_ctinfo
;
1570 ct
->c_ceopenfnx
= NULL
;
1571 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1572 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1573 struct str2init
*s2i
;
1574 CT p
= e
->eb_content
;
1576 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1577 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1580 e
->eb_access
= pm
->pm_value
;
1581 e
->eb_flags
= NOTOK
;
1582 p
->c_encoding
= CE_EXTERNAL
;
1585 e
->eb_access
= s2i
->si_key
;
1586 e
->eb_flags
= s2i
->si_val
;
1587 p
->c_encoding
= CE_EXTERNAL
;
1589 /* Call the Init function for this external type */
1590 if ((*s2i
->si_init
)(p
) == NOTOK
)
1594 if (!strcasecmp (pm
->pm_name
, "name")) {
1595 e
->eb_name
= pm
->pm_value
;
1598 if (!strcasecmp (pm
->pm_name
, "permission")) {
1599 e
->eb_permission
= pm
->pm_value
;
1602 if (!strcasecmp (pm
->pm_name
, "site")) {
1603 e
->eb_site
= pm
->pm_value
;
1606 if (!strcasecmp (pm
->pm_name
, "directory")) {
1607 e
->eb_dir
= pm
->pm_value
;
1610 if (!strcasecmp (pm
->pm_name
, "mode")) {
1611 e
->eb_mode
= pm
->pm_value
;
1614 if (!strcasecmp (pm
->pm_name
, "size")) {
1615 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1618 if (!strcasecmp (pm
->pm_name
, "server")) {
1619 e
->eb_server
= pm
->pm_value
;
1622 if (!strcasecmp (pm
->pm_name
, "subject")) {
1623 e
->eb_subject
= pm
->pm_value
;
1626 if (!strcasecmp (pm
->pm_name
, "url")) {
1628 * According to RFC 2017, we have to remove all whitespace from
1632 char *u
, *p
= pm
->pm_value
;
1633 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1635 for (; *p
!= '\0'; p
++) {
1636 if (! isspace((unsigned char) *p
))
1643 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1644 e
->eb_body
= getcpy (pm
->pm_value
);
1649 if (!e
->eb_access
) {
1650 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1651 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1664 InitApplication (CT ct
)
1666 CI ci
= &ct
->c_ctinfo
;
1669 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1676 * TRANSFER ENCODINGS
1680 init_encoding (CT ct
, OpenCEFunc openfnx
)
1682 ct
->c_ceopenfnx
= openfnx
;
1683 ct
->c_ceclosefnx
= close_encoding
;
1684 ct
->c_cesizefnx
= size_encoding
;
1691 close_encoding (CT ct
)
1693 CE ce
= &ct
->c_cefile
;
1702 static unsigned long
1703 size_encoding (CT ct
)
1708 CE ce
= &ct
->c_cefile
;
1711 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1712 return (long) st
.st_size
;
1715 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1716 return (long) st
.st_size
;
1720 if (ct
->c_encoding
== CE_EXTERNAL
)
1721 return (ct
->c_end
- ct
->c_begin
);
1724 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1725 return (ct
->c_end
- ct
->c_begin
);
1727 if (fstat (fd
, &st
) != NOTOK
)
1728 size
= (long) st
.st_size
;
1732 (*ct
->c_ceclosefnx
) (ct
);
1744 return init_encoding (ct
, openBase64
);
1749 openBase64 (CT ct
, char **file
)
1752 int fd
, own_ct_fp
= 0;
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 adios(NULL
, "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 adios(NULL
, "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 adios (NULL
, "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
) {
1846 unsigned char *decoded_p
= decoded
;
1847 for (i
= 0; i
< decoded_len
; ++i
) {
1848 putc (*decoded_p
++, ce
->ce_fp
);
1851 if (ferror (ce
->ce_fp
)) {
1852 content_error (ce
->ce_file
, ct
, "error writing to");
1856 if (ct
->c_digested
) {
1857 if (memcmp(digest
, ct
->c_digest
,
1859 content_error (NULL
, ct
,
1860 "content integrity suspect (digest mismatch) -- continuing");
1863 fprintf (stderr
, "content integrity confirmed\n");
1871 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1873 if (fflush (ce
->ce_fp
)) {
1874 content_error (ce
->ce_file
, ct
, "error writing to");
1878 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1881 *file
= ce
->ce_file
;
1887 return fileno (ce
->ce_fp
);
1894 free_encoding (ct
, 0);
1904 static char hex2nib
[0x80] = {
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1909 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1910 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1911 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1912 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1913 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 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, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1918 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1919 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1920 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1927 return init_encoding (ct
, openQuoted
);
1932 openQuoted (CT ct
, char **file
)
1934 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1940 CE ce
= &ct
->c_cefile
;
1941 /* sbeck -- handle suffixes */
1946 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1951 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1952 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1958 if (*file
== NULL
) {
1961 ce
->ce_file
= mh_xstrdup(*file
);
1965 /* sbeck@cise.ufl.edu -- handle suffixes */
1967 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1968 if (ce
->ce_unlink
) {
1969 /* Create temporary file with filename extension. */
1970 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1971 adios(NULL
, "unable to create temporary file in %s",
1975 ce
->ce_file
= add (cp
, ce
->ce_file
);
1977 } else if (*file
== NULL
) {
1979 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1980 adios(NULL
, "unable to create temporary file in %s",
1983 ce
->ce_file
= mh_xstrdup(tempfile
);
1986 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1987 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1991 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1992 adios (NULL
, "internal error(2)");
1995 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1996 content_error (ct
->c_file
, ct
, "unable to open for reading");
2002 if ((digested
= ct
->c_digested
))
2003 MD5Init (&mdContext
);
2010 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2012 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2013 content_error (NULL
, ct
, "premature eof");
2017 if ((cc
= gotlen
) > len
)
2021 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2022 if (!isspace ((unsigned char) *ep
))
2027 for (; cp
< ep
; cp
++) {
2029 /* in an escape sequence */
2031 /* at byte 1 of an escape sequence */
2032 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2033 /* next is byte 2 */
2036 /* at byte 2 of an escape sequence */
2038 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2039 putc (mask
, ce
->ce_fp
);
2041 MD5Update (&mdContext
, &mask
, 1);
2042 if (ferror (ce
->ce_fp
)) {
2043 content_error (ce
->ce_file
, ct
, "error writing to");
2046 /* finished escape sequence; next may be literal or a new
2047 * escape sequence */
2050 /* on to next byte */
2054 /* not in an escape sequence */
2056 /* starting an escape sequence, or invalid '='? */
2057 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2058 /* "=\n" soft line break, eat the \n */
2062 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2063 /* We don't have 2 bytes left, so this is an invalid
2064 * escape sequence; just show the raw bytes (below). */
2065 } else if (isxdigit ((unsigned char) cp
[1]) &&
2066 isxdigit ((unsigned char) cp
[2])) {
2067 /* Next 2 bytes are hex digits, making this a valid escape
2068 * sequence; let's decode it (above). */
2072 /* One or both of the next 2 is out of range, making this
2073 * an invalid escape sequence; just show the raw bytes
2077 /* Just show the raw byte. */
2078 putc (*cp
, ce
->ce_fp
);
2081 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2083 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2086 if (ferror (ce
->ce_fp
)) {
2087 content_error (ce
->ce_file
, ct
, "error writing to");
2093 content_error (NULL
, ct
,
2094 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2098 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2100 if (fflush (ce
->ce_fp
)) {
2101 content_error (ce
->ce_file
, ct
, "error writing to");
2106 unsigned char digest
[16];
2108 MD5Final (digest
, &mdContext
);
2109 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2111 content_error (NULL
, ct
,
2112 "content integrity suspect (digest mismatch) -- continuing");
2115 fprintf (stderr
, "content integrity confirmed\n");
2118 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2121 *file
= ce
->ce_file
;
2127 return fileno (ce
->ce_fp
);
2130 free_encoding (ct
, 0);
2147 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2150 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2156 open7Bit (CT ct
, char **file
)
2158 int cc
, fd
, len
, own_ct_fp
= 0;
2159 char buffer
[BUFSIZ
];
2160 /* sbeck -- handle suffixes */
2163 CE ce
= &ct
->c_cefile
;
2166 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2171 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2172 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2178 if (*file
== NULL
) {
2181 ce
->ce_file
= mh_xstrdup(*file
);
2185 /* sbeck@cise.ufl.edu -- handle suffixes */
2187 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2188 if (ce
->ce_unlink
) {
2189 /* Create temporary file with filename extension. */
2190 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2191 adios(NULL
, "unable to create temporary file in %s",
2195 ce
->ce_file
= add (cp
, ce
->ce_file
);
2197 } else if (*file
== NULL
) {
2199 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2200 adios(NULL
, "unable to create temporary file in %s",
2203 ce
->ce_file
= mh_xstrdup(tempfile
);
2206 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2207 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2211 if (ct
->c_type
== CT_MULTIPART
) {
2212 CI ci
= &ct
->c_ctinfo
;
2216 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2217 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2218 + 1 + strlen (ci
->ci_subtype
);
2219 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2222 fputs (buffer
, ce
->ce_fp
);
2226 if (ci
->ci_comment
) {
2227 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2228 fputs ("\n\t", ce
->ce_fp
);
2232 putc (' ', ce
->ce_fp
);
2235 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2238 fprintf (ce
->ce_fp
, "\n");
2240 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2242 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2244 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2245 fprintf (ce
->ce_fp
, "\n");
2248 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2249 adios (NULL
, "internal error(3)");
2252 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2253 content_error (ct
->c_file
, ct
, "unable to open for reading");
2259 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2261 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2263 content_error (ct
->c_file
, ct
, "error reading from");
2267 content_error (NULL
, ct
, "premature eof");
2275 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2276 advise ("open7Bit", "fwrite");
2278 if (ferror (ce
->ce_fp
)) {
2279 content_error (ce
->ce_file
, ct
, "error writing to");
2284 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2286 if (fflush (ce
->ce_fp
)) {
2287 content_error (ce
->ce_file
, ct
, "error writing to");
2291 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2294 *file
= ce
->ce_file
;
2299 return fileno (ce
->ce_fp
);
2302 free_encoding (ct
, 0);
2316 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2318 char cachefile
[BUFSIZ
];
2321 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2326 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2327 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2333 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2334 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2335 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2336 ce
->ce_file
= mh_xstrdup(cachefile
);
2340 admonish (cachefile
, "unable to fopen for reading");
2343 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2347 *file
= ce
->ce_file
;
2348 *fd
= fileno (ce
->ce_fp
);
2359 return init_encoding (ct
, openFile
);
2364 openFile (CT ct
, char **file
)
2367 char cachefile
[BUFSIZ
];
2368 struct exbody
*e
= ct
->c_ctexbody
;
2369 CE ce
= &ct
->c_cefile
;
2371 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2383 content_error (NULL
, ct
, "missing name parameter");
2387 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2390 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2391 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2395 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2396 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2397 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2401 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2402 if ((fp
= fopen (cachefile
, "w"))) {
2404 char buffer
[BUFSIZ
];
2405 FILE *gp
= ce
->ce_fp
;
2407 fseek (gp
, 0L, SEEK_SET
);
2409 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2411 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2412 advise ("openFile", "fwrite");
2417 admonish (ce
->ce_file
, "error reading");
2418 (void) m_unlink (cachefile
);
2422 admonish (cachefile
, "error writing");
2423 (void) m_unlink (cachefile
);
2430 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2431 *file
= ce
->ce_file
;
2432 return fileno (ce
->ce_fp
);
2442 return init_encoding (ct
, openFTP
);
2447 openFTP (CT ct
, char **file
)
2449 int cachetype
, caching
, fd
;
2451 char *bp
, *ftp
, *user
, *pass
;
2452 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2454 CE ce
= &ct
->c_cefile
;
2455 static char *username
= NULL
;
2456 static char *password
= NULL
;
2460 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2466 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2477 if (!e
->eb_name
|| !e
->eb_site
) {
2478 content_error (NULL
, ct
, "missing %s parameter",
2479 e
->eb_name
? "site": "name");
2483 /* Get the buffer ready to go */
2485 buflen
= sizeof(buffer
);
2488 * Construct the query message for user
2490 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2496 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2502 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2503 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2508 if (e
->eb_size
> 0) {
2509 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2514 snprintf (bp
, buflen
, "? ");
2517 * Now, check the answer
2519 if (!read_yes_or_no_if_tty (buffer
))
2524 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2528 ruserpass (e
->eb_site
, &username
, &password
, 0);
2533 ce
->ce_unlink
= (*file
== NULL
);
2535 cachefile
[0] = '\0';
2536 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2537 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2538 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2539 if (*file
== NULL
) {
2546 ce
->ce_file
= mh_xstrdup(*file
);
2548 ce
->ce_file
= mh_xstrdup(cachefile
);
2551 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2552 adios(NULL
, "unable to create temporary file in %s",
2555 ce
->ce_file
= mh_xstrdup(tempfile
);
2558 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2559 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2564 int child_id
, i
, vecp
;
2568 vec
[vecp
++] = r1bindex (ftp
, '/');
2569 vec
[vecp
++] = e
->eb_site
;
2572 vec
[vecp
++] = e
->eb_dir
;
2573 vec
[vecp
++] = e
->eb_name
;
2574 vec
[vecp
++] = ce
->ce_file
,
2575 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2576 ? "ascii" : "binary";
2581 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2585 adios ("fork", "unable to");
2589 close (fileno (ce
->ce_fp
));
2591 fprintf (stderr
, "unable to exec ");
2597 if (pidXwait (child_id
, NULL
)) {
2598 username
= password
= NULL
;
2608 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2613 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2614 if ((fp
= fopen (cachefile
, "w"))) {
2616 FILE *gp
= ce
->ce_fp
;
2618 fseek (gp
, 0L, SEEK_SET
);
2620 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2622 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2623 advise ("openFTP", "fwrite");
2628 admonish (ce
->ce_file
, "error reading");
2629 (void) m_unlink (cachefile
);
2633 admonish (cachefile
, "error writing");
2634 (void) m_unlink (cachefile
);
2642 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2643 *file
= ce
->ce_file
;
2644 return fileno (ce
->ce_fp
);
2655 return init_encoding (ct
, openMail
);
2660 openMail (CT ct
, char **file
)
2662 int child_id
, fd
, i
, vecp
;
2664 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2665 struct exbody
*e
= ct
->c_ctexbody
;
2666 CE ce
= &ct
->c_cefile
;
2668 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2679 if (!e
->eb_server
) {
2680 content_error (NULL
, ct
, "missing server parameter");
2684 /* Get buffer ready to go */
2686 buflen
= sizeof(buffer
);
2688 /* Now, construct query message */
2689 snprintf (bp
, buflen
, "Retrieve content");
2695 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2701 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2703 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2705 /* Now, check answer */
2706 if (!read_yes_or_no_if_tty (buffer
))
2710 vec
[vecp
++] = r1bindex (mailproc
, '/');
2711 vec
[vecp
++] = e
->eb_server
;
2712 vec
[vecp
++] = "-subject";
2713 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2714 vec
[vecp
++] = "-body";
2715 vec
[vecp
++] = e
->eb_body
;
2718 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
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 adios(NULL
, "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 mh_xfree(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
];
2785 int fd
, caching
, cachetype
;
2786 struct msgs_array args
= { 0, 0, NULL
};
2789 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2793 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2797 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2809 content_error(NULL
, ct
, "missing url parameter");
2813 ce
->ce_unlink
= (*file
== NULL
);
2815 cachefile
[0] = '\0';
2817 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2818 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2819 if (*file
== NULL
) {
2826 ce
->ce_file
= mh_xstrdup(*file
);
2828 ce
->ce_file
= mh_xstrdup(cachefile
);
2831 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2832 adios(NULL
, "unable to create temporary file in %s",
2835 ce
->ce_file
= mh_xstrdup(tempfile
);
2838 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2839 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2843 switch (child_id
= fork()) {
2845 adios ("fork", "unable to");
2849 argsplit_msgarg(&args
, urlprog
, &program
);
2850 app_msgarg(&args
, e
->eb_url
);
2851 app_msgarg(&args
, NULL
);
2852 dup2(fileno(ce
->ce_fp
), 1);
2853 close(fileno(ce
->ce_fp
));
2854 execvp(program
, args
.msgs
);
2855 fprintf(stderr
, "Unable to exec ");
2861 if (pidXwait(child_id
, NULL
)) {
2869 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2874 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2875 if ((fp
= fopen(cachefile
, "w"))) {
2877 FILE *gp
= ce
->ce_fp
;
2879 fseeko(gp
, 0, SEEK_SET
);
2881 while ((cc
= fread(buffer
, sizeof(*buffer
),
2882 sizeof(buffer
), gp
)) > 0)
2883 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2884 advise ("openURL", "fwrite");
2890 admonish(ce
->ce_file
, "error reading");
2891 (void) m_unlink (cachefile
);
2898 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2899 *file
= ce
->ce_file
;
2905 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2906 * has to be base64 decoded.
2909 readDigest (CT ct
, char *cp
)
2911 unsigned char *digest
;
2914 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2915 const size_t maxlen
= sizeof ct
->c_digest
;
2917 if (strlen ((char *) digest
) <= maxlen
) {
2918 memcpy (ct
->c_digest
, digest
, maxlen
);
2923 fprintf (stderr
, "MD5 digest=");
2924 for (i
= 0; i
< maxlen
; ++i
) {
2925 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2927 fprintf (stderr
, "\n");
2933 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2934 (int) strlen ((char *) digest
));
2944 /* Multipart parts might have content before the first subpart and/or
2945 after the last subpart that hasn't been stored anywhere else, so do
2948 get_leftover_mp_content (CT ct
, int before
/* or after */)
2950 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2952 int found_boundary
= 0;
2958 char *content
= NULL
;
2960 if (! m
) return NOTOK
;
2963 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2965 /* Isolate the beginning of this part to the beginning of the
2966 first subpart and save any content between them. */
2967 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2968 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2969 boundary
= concat ("--", m
->mp_start
, NULL
);
2971 struct part
*last_subpart
= NULL
;
2972 struct part
*subpart
;
2974 /* Go to the last subpart to get its end position. */
2975 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2976 last_subpart
= subpart
;
2979 if (last_subpart
== NULL
) return NOTOK
;
2981 /* Isolate the end of the last subpart to the end of this part
2982 and save any content between them. */
2983 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2984 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2985 boundary
= concat ("--", m
->mp_stop
, NULL
);
2988 /* Back up by 1 to pick up the newline. */
2989 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2991 /* Don't look beyond beginning of first subpart (before) or
2992 next part (after). */
2993 if (read
> max
) bufp
[read
-max
] = '\0';
2996 if (! strcmp (bufp
, boundary
)) {
3000 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3006 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3008 char *old_content
= content
;
3009 content
= concat (content
, bufp
, NULL
);
3013 ? concat ("\n", bufp
, NULL
)
3014 : concat (bufp
, NULL
);
3019 if (found_boundary
|| read
> max
) break;
3021 if (read
> max
) break;
3025 /* Skip the newline if that's all there is. */
3029 /* Remove trailing newline, except at EOF. */
3030 if ((before
|| ! feof (ct
->c_fp
)) &&
3031 (cp
= content
+ strlen (content
)) > content
&&
3036 if (strlen (content
) > 1) {
3038 m
->mp_content_before
= content
;
3040 m
->mp_content_after
= content
;
3055 ct_type_str (int type
) {
3057 case CT_APPLICATION
:
3058 return "application";
3074 return "unknown_type";
3080 ct_subtype_str (int type
, int subtype
) {
3082 case CT_APPLICATION
:
3084 case APPLICATION_OCTETS
:
3086 case APPLICATION_POSTSCRIPT
:
3087 return "postscript";
3089 return "unknown_app_subtype";
3093 case MESSAGE_RFC822
:
3095 case MESSAGE_PARTIAL
:
3097 case MESSAGE_EXTERNAL
:
3100 return "unknown_msg_subtype";
3106 case MULTI_ALTERNATE
:
3107 return "alternative";
3110 case MULTI_PARALLEL
:
3115 return "unknown_multipart_subtype";
3126 return "unknown_text_subtype";
3129 return "unknown_type";
3135 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
) {
3156 case CT_APPLICATION
:
3157 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3158 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3162 return kv
->kv_value
;
3164 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3165 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3169 return kv
->kv_value
;
3171 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3172 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3176 return kv
->kv_value
;
3178 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3179 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3183 return kv
->kv_value
;
3190 /* Find the content type and InitFunc for the CT. */
3191 const struct str2init
*
3192 get_ct_init (int type
) {
3193 const struct str2init
*sp
;
3195 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3196 if (type
== sp
->si_val
) {
3205 ce_str (int encoding
) {
3210 return "quoted-printable";
3226 /* Find the content type and InitFunc for the content encoding method. */
3227 const struct str2init
*
3228 get_ce_method (const char *method
) {
3229 struct str2init
*sp
;
3231 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3232 if (! strcasecmp (method
, sp
->si_key
)) {
3241 * Parse a series of MIME attributes (or parameters) given a header as
3244 * Arguments include:
3246 * filename - Name of input file (for error messages)
3247 * fieldname - Name of field being processed
3248 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3249 * Updated to point to end of attributes when finished.
3250 * param_head - Pointer to head of parameter list
3251 * param_tail - Pointer to tail of parameter list
3252 * commentp - Pointer to header comment pointer (may be NULL)
3254 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3255 * DONE to indicate a benign error (minor parsing error, but the program
3260 parse_header_attrs (const char *filename
, const char *fieldname
,
3261 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3264 char *cp
= *header_attrp
;
3270 struct sectlist
*next
;
3276 struct sectlist
*sechead
;
3277 struct parmlist
*next
;
3278 } *pp
, *pp2
, *phead
= NULL
;
3280 while (*cp
== ';') {
3281 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3282 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3285 while (isspace ((unsigned char) *cp
))
3289 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3294 if (! suppress_extraneous_trailing_semicolon_warning
) {
3295 inform("extraneous trailing ';' in message %s's %s: "
3296 "parameter list", filename
, fieldname
);
3301 /* down case the attribute name */
3302 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3303 *dp
= tolower ((unsigned char) *dp
);
3305 for (up
= dp
; isspace ((unsigned char) *dp
);)
3307 if (dp
== cp
|| *dp
!= '=') {
3308 inform("invalid parameter in message %s's %s: field\n"
3309 " parameter %s (error detected at offset %ld)",
3310 filename
, fieldname
, cp
, (long)(dp
- cp
));
3315 * To handle RFC 2231, we have to deal with the following extensions:
3317 * name*=encoded-value
3318 * name*<N>=part-N-of-a-parameter-value
3319 * name*<N>*=encoded-part-N-of-a-parameter-value
3322 * If there's a * right before the equal sign, it's encoded.
3323 * If there's a * and one or more digits, then it's section N.
3325 * Remember we can have one or the other, or both. cp points to
3326 * beginning of name, up points past the last character in the
3330 for (vp
= cp
; vp
< up
; vp
++) {
3331 if (*vp
== '*' && vp
< up
- 1) {
3335 if (*vp
== '*' && vp
== up
- 1) {
3337 } else if (partial
) {
3338 if (isdigit((unsigned char) *vp
))
3339 index
= *vp
- '0' + index
* 10;
3341 inform("invalid parameter index in message %s's %s: field"
3342 "\n (parameter %s)", filename
, fieldname
, cp
);
3351 * Break out the parameter name and value sections and allocate
3355 nameptr
= mh_xmalloc(len
+ 1);
3356 strncpy(nameptr
, cp
, len
);
3357 nameptr
[len
] = '\0';
3359 for (dp
++; isspace ((unsigned char) *dp
);)
3364 * Single quotes delimit the character set and language tag.
3365 * They are required on the first section (or a complete
3370 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3376 charset
= mh_xmalloc(len
+ 1);
3377 strncpy(charset
, dp
, len
);
3378 charset
[len
] = '\0';
3384 inform("missing charset in message %s's %s: field\n"
3385 " (parameter %s)", filename
, fieldname
, nameptr
);
3391 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3398 lang
= mh_xmalloc(len
+ 1);
3399 strncpy(lang
, dp
, len
);
3406 inform("missing language tag in message %s's %s: field\n"
3407 " (parameter %s)", filename
, fieldname
, nameptr
);
3417 * At this point vp should be pointing at the beginning
3418 * of the encoded value/section. Continue until we reach
3419 * the end or get whitespace. But first, calculate the
3420 * length so we can allocate the correct buffer size.
3423 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3425 if (*(vp
+ 1) == '\0' ||
3426 !isxdigit((unsigned char) *(vp
+ 1)) ||
3427 *(vp
+ 2) == '\0' ||
3428 !isxdigit((unsigned char) *(vp
+ 2))) {
3429 inform("invalid encoded sequence in message %s's %s: field\n"
3430 " (parameter %s)", filename
, fieldname
, nameptr
);
3441 up
= valptr
= mh_xmalloc(len
+ 1);
3443 for (vp
= dp
; istoken(*vp
); vp
++) {
3445 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3456 * A "normal" string. If it's got a leading quote, then we
3457 * strip the quotes out. Otherwise go until we reach the end
3458 * or get whitespace. Note we scan it twice; once to get the
3459 * length, then the second time copies it into the destination
3466 for (cp
= dp
+ 1;;) {
3470 inform("invalid quoted-string in message %s's %s: field\n"
3471 " (parameter %s)", filename
, fieldname
, nameptr
);
3491 for (cp
= dp
; istoken (*cp
); cp
++) {
3496 valptr
= mh_xmalloc(len
+ 1);
3500 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3508 strncpy(valptr
, cp
= dp
, len
);
3516 * If 'partial' is set, we don't allocate a parameter now. We
3517 * put it on the parameter linked list to be reassembled later.
3519 * "phead" points to a list of all parameters we need to reassemble.
3520 * Each parameter has a list of sections. We insert the sections in
3525 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3526 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3541 * Insert this into the section linked list
3549 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3550 sp
->next
= pp
->sechead
;
3553 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3554 if (sp2
->index
== sp
->index
) {
3555 inform("duplicate index (%d) in message %s's %s: field"
3556 "\n (parameter %s)", sp
->index
, filename
,
3557 fieldname
, nameptr
);
3560 if (sp2
->index
< sp
->index
&&
3561 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3562 sp
->next
= sp2
->next
;
3569 inform("Internal error: cannot insert partial param "
3570 "in message %s's %s: field\n (parameter %s)",
3571 filename
, fieldname
, nameptr
);
3577 * Save our charset and lang tags.
3580 if (index
== 0 && encoded
) {
3581 mh_xfree(pp
->charset
);
3582 pp
->charset
= charset
;
3587 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3588 pm
->pm_charset
= charset
;
3592 while (isspace ((unsigned char) *cp
))
3596 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3602 * Now that we're done, reassemble all of the partial parameters.
3605 for (pp
= phead
; pp
!= NULL
; ) {
3609 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3610 if (sp
->index
!= pindex
++) {
3611 inform("missing section %d for parameter in message "
3612 "%s's %s: field\n (parameter %s)", pindex
- 1,
3613 filename
, fieldname
, pp
->name
);
3619 p
= q
= mh_xmalloc(tlen
+ 1);
3620 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3621 memcpy(q
, sp
->value
, sp
->len
);
3631 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3632 pm
->pm_charset
= pp
->charset
;
3633 pm
->pm_lang
= pp
->lang
;
3644 * Return the charset for a particular content type.
3648 content_charset (CT ct
) {
3649 char *ret_charset
= NULL
;
3651 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3653 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3658 * Create a string based on a list of output parameters. Assume that this
3659 * parameter string will be appended to an existing header, so start out
3660 * with the separator (;). Perform RFC 2231 encoding when necessary.
3664 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3666 char *paramout
= NULL
;
3667 char line
[CPERLIN
* 2], *q
;
3668 int curlen
, index
, cont
, encode
, i
;
3669 size_t valoff
, numchars
;
3671 while (params
!= NULL
) {
3677 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3680 if (strlen(params
->pm_name
) > CPERLIN
) {
3681 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3686 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3689 * Loop until we get a parameter that fits within a line. We
3690 * assume new lines start with a tab, so check our overflow based
3700 * At this point we're definitely continuing the line, so
3701 * be sure to include the parameter name and section index.
3704 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3705 params
->pm_name
, index
);
3708 * Both of these functions do a NUL termination
3712 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3713 numchars
, valoff
, index
);
3715 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3725 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3730 * "line" starts with a ;\n\t, so that doesn't count against
3731 * the length. But add 8 since it starts with a tab; that's
3732 * how we end up with 5.
3735 initialwidth
= strlen(line
) + 5;
3738 * At this point the line should be built, so add it to our
3739 * current output buffer.
3742 paramout
= add(line
, paramout
);
3746 * If this won't fit on the line, start a new one. Save room in
3747 * case we need a semicolon on the end
3750 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3762 * At this point, we're either finishing a continued parameter, or
3763 * we're working on a new one.
3767 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3768 params
->pm_name
, index
);
3770 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3775 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3776 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3778 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3779 strlen(params
->pm_value
+ valoff
), valoff
);
3786 paramout
= add(line
, paramout
);
3787 initialwidth
+= strlen(line
);
3789 params
= params
->pm_next
;
3793 *offsetout
= initialwidth
;
3799 * Calculate the size of a parameter.
3803 * pm - The parameter being output
3804 * index - If continuing the parameter, the index of the section
3806 * valueoff - The current offset into the parameter value that we're
3807 * working on (previous sections have consumed valueoff bytes).
3808 * encode - Set if we should perform encoding on this parameter section
3809 * (given that we're consuming bytesfit bytes).
3810 * cont - Set if the remaining data in value will not fit on a single
3811 * line and will need to be continued.
3812 * bytesfit - The number of bytes that we can consume from the parameter
3813 * value and still fit on a completely new line. The
3814 * calculation assumes the new line starts with a tab,
3815 * includes the parameter name and any encoding, and fits
3816 * within CPERLIN bytes. Will always be at least 1.
3820 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3823 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3824 size_t len
= 0, fit
= 0;
3825 int fitlimit
= 0, eightbit
, maxfit
;
3830 * Add up the length. First, start with the parameter name.
3833 len
= strlen(pm
->pm_name
);
3836 * Scan the parameter value and see if we need to do encoding for this
3840 eightbit
= contains8bit(start
, NULL
);
3843 * Determine if we need to encode this section. Encoding is necessary if:
3845 * - There are any 8-bit characters at all and we're on the first
3847 * - There are 8-bit characters within N bytes of our section start.
3848 * N is calculated based on the number of bytes it would take to
3849 * reach CPERLIN. Specifically:
3850 * 8 (starting tab) +
3851 * strlen(param name) +
3852 * 4 ('* for section marker, '=', opening/closing '"')
3854 * is the number of bytes used by everything that isn't part of the
3855 * value. So that gets subtracted from CPERLIN.
3858 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3859 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3860 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3864 len
++; /* Add in equal sign */
3868 * We're using maxfit as a marker for how many characters we can
3869 * fit into the line. Bump it by two because we're not using quotes
3876 * If we don't have a charset or language tag in this parameter,
3880 if (! pm
->pm_charset
) {
3881 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3882 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3883 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3884 "local character set is US-ASCII", pm
->pm_name
);
3887 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3889 len
++; /* For the encoding marker */
3892 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3897 * We know we definitely need to include an index. maxfit already
3898 * includes the section marker.
3900 len
+= strlen(indexchar
);
3902 for (p
= start
; *p
!= '\0'; p
++) {
3903 if (isparamencode(*p
)) {
3911 * Just so there's no confusion: maxfit is counting OUTPUT
3912 * characters (post-encoding). fit is counting INPUT characters.
3914 if (! fitlimit
&& maxfit
>= 0)
3916 else if (! fitlimit
)
3921 * Calculate the string length, but add room for quoting \
3922 * and " if necessary. Also account for quotes at beginning
3925 for (p
= start
; *p
!= '\0'; p
++) {
3936 if (! fitlimit
&& maxfit
>= 0)
3938 else if (! fitlimit
)
3955 * Output an encoded parameter string.
3959 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3960 size_t valueoff
, int index
)
3962 size_t outlen
= 0, n
;
3963 char *endptr
= output
+ len
, *p
;
3966 * First, output the marker for an encoded string.
3974 * If the index is 0, output the character set and language tag.
3975 * If theses were NULL, they should have already been filled in
3980 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3984 if (output
> endptr
) {
3985 inform("Internal error: parameter buffer overflow");
3991 * Copy over the value, encoding if necessary
3994 p
= pm
->pm_value
+ valueoff
;
3995 while (valuelen
-- > 0) {
3996 if (isparamencode(*p
)) {
3997 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4004 if (output
> endptr
) {
4005 inform("Internal error: parameter buffer overflow");
4016 * Output a "normal" parameter, without encoding. Be sure to escape
4017 * quotes and backslashes if necessary.
4021 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4025 char *endptr
= output
+ len
, *p
;
4031 p
= pm
->pm_value
+ valueoff
;
4033 while (valuelen
-- > 0) {
4044 if (output
> endptr
) {
4045 inform("Internal error: parameter buffer overflow");
4050 if (output
- 2 > endptr
) {
4051 inform("Internal error: parameter buffer overflow");
4062 * Add a parameter to the parameter linked list
4066 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4071 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4072 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4075 (*last
)->pm_next
= pm
;
4086 * Either replace a current parameter with a new value, or add the parameter
4087 * to the parameter linked list.
4091 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4095 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4096 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4098 * If nocopy is set, it's assumed that we own both name
4099 * and value. We don't need name, so we discard it now.
4104 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4109 return add_param(first
, last
, name
, value
, nocopy
);
4113 * Retrieve a parameter value from a parameter linked list. If the parameter
4114 * value needs converted to the local character set, do that now.
4118 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4120 while (first
!= NULL
) {
4121 if (strcasecmp(name
, first
->pm_name
) == 0) {
4123 return first
->pm_value
;
4124 return getcpy(get_param_value(first
, replace
));
4126 first
= first
->pm_next
;
4133 * Return a parameter value, converting to the local character set if
4137 char *get_param_value(PM pm
, char replace
)
4139 static char buffer
[4096]; /* I hope no parameters are larger */
4140 size_t bufsize
= sizeof(buffer
);
4145 ICONV_CONST
char *p
;
4146 #else /* HAVE_ICONV */
4148 #endif /* HAVE_ICONV */
4153 * If we don't have a character set indicated, it's assumed to be
4154 * US-ASCII. If it matches our character set, we don't need to convert
4158 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4159 strlen(pm
->pm_charset
))) {
4160 return pm
->pm_value
;
4164 * In this case, we need to convert. If we have iconv support, use
4165 * that. Otherwise, go through and simply replace every non-ASCII
4166 * character with the substitution character.
4171 bufsize
= sizeof(buffer
);
4172 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4174 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4175 if (cd
== (iconv_t
) -1) {
4179 inbytes
= strlen(pm
->pm_value
);
4183 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4184 if (errno
!= EILSEQ
) {
4189 * Reset shift state, substitute our character,
4190 * try to restart conversion.
4193 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4206 for (++p
, --inbytes
;
4207 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4226 #endif /* HAVE_ICONV */
4229 * Take everything non-ASCII and substitute the replacement character
4233 bufsize
= sizeof(buffer
);
4234 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4235 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))