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/context_find.h"
10 #include "sbr/pidstatus.h"
11 #include "sbr/arglist.h"
12 #include "sbr/error.h"
18 #include "h/mhparse.h"
21 #include "h/mhcachesbr.h"
22 #include "sbr/m_mktemp.h"
26 #endif /* HAVE_ICONV */
27 #include "sbr/base64.h"
32 int checksw
= 0; /* check Content-MD5 field */
35 * These are for mhfixmsg to:
36 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
38 * 2) Suppress the warning about bogus multipart content, and report it.
39 * 3) Suppress the warning about extraneous trailing ';' in header parameter
42 bool skip_mp_cte_check
;
43 bool suppress_bogus_mp_content_warning
;
44 bool bogus_mp_content
;
45 bool suppress_extraneous_trailing_semicolon_warning
;
48 * By default, suppress warning about multiple MIME-Version header fields.
50 bool suppress_multiple_mime_version_warning
= true;
52 /* list of preferred type/subtype pairs, for -prefer */
53 mime_type_subtype mime_preference
[NPREFS
];
58 * Structures for TEXT messages
60 struct k2v SubText
[] = {
61 { "plain", TEXT_PLAIN
},
62 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
63 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
64 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
67 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
70 * Structures for MULTIPART messages
72 struct k2v SubMultiPart
[] = {
73 { "mixed", MULTI_MIXED
},
74 { "alternative", MULTI_ALTERNATE
},
75 { "digest", MULTI_DIGEST
},
76 { "parallel", MULTI_PARALLEL
},
77 { "related", MULTI_RELATED
},
78 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
82 * Structures for MESSAGE messages
84 struct k2v SubMessage
[] = {
85 { "rfc822", MESSAGE_RFC822
},
86 { "partial", MESSAGE_PARTIAL
},
87 { "external-body", MESSAGE_EXTERNAL
},
88 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
92 * Structure for APPLICATION messages
94 struct k2v SubApplication
[] = {
95 { "octet-stream", APPLICATION_OCTETS
},
96 { "postscript", APPLICATION_POSTSCRIPT
},
97 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
101 * Mapping of names of CTE types in mhbuild directives
103 static struct k2v EncodingType
[] = {
106 { "q-p", CE_QUOTED
},
107 { "quoted-printable", CE_QUOTED
},
108 { "b64", CE_BASE64
},
109 { "base64", CE_BASE64
},
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_val 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
= false;
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
= mh_xstrdup(FENDNULL(file
));
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 die("message format error in component #%d", compnum
);
371 die("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
= mh_xstrdup(FENDNULL(hp
->value
));
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
= mh_xstrdup(FENDNULL(hp
->value
));
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
= mh_xstrdup(FENDNULL(hp
->value
)); /* 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
= mh_xstrdup(FENDNULL(cp
));
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
= mh_xstrdup(FENDNULL(cp
));
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
; 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
= true;
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
, mime_type_subtype
*pref
)
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
, pref
->type
) &&
1349 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1350 prev
->mp_next
= part
->mp_next
;
1351 part
->mp_next
= NULL
;
1352 ntail
->mp_next
= part
;
1354 part
= prev
->mp_next
;
1357 part
= prev
->mp_next
;
1360 ntail
->mp_next
= head
->mp_next
;
1361 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
= 0; i
< npreferred
; i
++)
1374 move_preferred_part(ct
, mime_preference
+ 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
)
1386 if (ct
->c_type
== CT_MULTIPART
) {
1387 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1390 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1394 /* And call recursively on each part of a multipart. */
1395 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1396 reverse_alternative_parts (part
->mp_part
);
1409 CI ci
= &ct
->c_ctinfo
;
1411 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1412 inform("\"%s/%s\" type in message %s should be encoded in "
1413 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1418 /* check for missing subtype */
1419 if (!*ci
->ci_subtype
)
1420 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1423 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1425 switch (ct
->c_subtype
) {
1426 case MESSAGE_RFC822
:
1429 case MESSAGE_PARTIAL
:
1435 ct
->c_ctparams
= (void *) p
;
1437 /* scan for parameters "id", "number", and "total" */
1438 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1439 if (!strcasecmp (pm
->pm_name
, "id")) {
1440 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1443 if (!strcasecmp (pm
->pm_name
, "number")) {
1444 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1445 || p
->pm_partno
< 1) {
1447 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1448 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1449 ct
->c_file
, TYPE_FIELD
);
1454 if (!strcasecmp (pm
->pm_name
, "total")) {
1455 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1464 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1465 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1466 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1472 case MESSAGE_EXTERNAL
:
1480 ct
->c_ctparams
= (void *) e
;
1483 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1484 advise (ct
->c_file
, "unable to open for reading");
1488 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1490 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1498 p
->c_ceopenfnx
= NULL
;
1499 if ((exresult
= params_external (ct
, 0)) != NOTOK
1500 && p
->c_ceopenfnx
== openMail
) {
1504 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1506 content_error (NULL
, ct
,
1507 "empty body for access-type=mail-server");
1511 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1512 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1514 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1516 adios ("failed", "fread");
1519 die("unexpected EOF from fread");
1522 bp
+= cc
, size
-= cc
;
1529 p
->c_end
= p
->c_begin
;
1534 if (exresult
== NOTOK
)
1536 if (e
->eb_flags
== NOTOK
)
1539 switch (p
->c_type
) {
1544 if (p
->c_subtype
!= MESSAGE_RFC822
)
1548 e
->eb_partno
= ct
->c_partno
;
1550 (*p
->c_ctinitfnx
) (p
);
1565 params_external (CT ct
, int composing
)
1568 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1569 CI ci
= &ct
->c_ctinfo
;
1571 ct
->c_ceopenfnx
= NULL
;
1572 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1573 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1574 struct str2init
*s2i
;
1575 CT p
= e
->eb_content
;
1577 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1578 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1581 e
->eb_access
= pm
->pm_value
;
1582 e
->eb_flags
= NOTOK
;
1583 p
->c_encoding
= CE_EXTERNAL
;
1586 e
->eb_access
= s2i
->si_key
;
1587 e
->eb_flags
= s2i
->si_val
;
1588 p
->c_encoding
= CE_EXTERNAL
;
1590 /* Call the Init function for this external type */
1591 if ((*s2i
->si_init
)(p
) == NOTOK
)
1595 if (!strcasecmp (pm
->pm_name
, "name")) {
1596 e
->eb_name
= pm
->pm_value
;
1599 if (!strcasecmp (pm
->pm_name
, "permission")) {
1600 e
->eb_permission
= pm
->pm_value
;
1603 if (!strcasecmp (pm
->pm_name
, "site")) {
1604 e
->eb_site
= pm
->pm_value
;
1607 if (!strcasecmp (pm
->pm_name
, "directory")) {
1608 e
->eb_dir
= pm
->pm_value
;
1611 if (!strcasecmp (pm
->pm_name
, "mode")) {
1612 e
->eb_mode
= pm
->pm_value
;
1615 if (!strcasecmp (pm
->pm_name
, "size")) {
1616 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1619 if (!strcasecmp (pm
->pm_name
, "server")) {
1620 e
->eb_server
= pm
->pm_value
;
1623 if (!strcasecmp (pm
->pm_name
, "subject")) {
1624 e
->eb_subject
= pm
->pm_value
;
1627 if (!strcasecmp (pm
->pm_name
, "url")) {
1629 * According to RFC 2017, we have to remove all whitespace from
1633 char *u
, *p
= pm
->pm_value
;
1634 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1636 for (; *p
!= '\0'; p
++) {
1637 if (! isspace((unsigned char) *p
))
1644 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1645 e
->eb_body
= getcpy (pm
->pm_value
);
1650 if (!e
->eb_access
) {
1651 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1652 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1665 InitApplication (CT ct
)
1667 CI ci
= &ct
->c_ctinfo
;
1670 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1677 * TRANSFER ENCODINGS
1681 init_encoding (CT ct
, OpenCEFunc openfnx
)
1683 ct
->c_ceopenfnx
= openfnx
;
1684 ct
->c_ceclosefnx
= close_encoding
;
1685 ct
->c_cesizefnx
= size_encoding
;
1692 close_encoding (CT ct
)
1694 CE ce
= &ct
->c_cefile
;
1703 static unsigned long
1704 size_encoding (CT ct
)
1709 CE ce
= &ct
->c_cefile
;
1712 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1713 return (long) st
.st_size
;
1716 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1717 return (long) st
.st_size
;
1721 if (ct
->c_encoding
== CE_EXTERNAL
)
1722 return ct
->c_end
- ct
->c_begin
;
1725 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1726 return ct
->c_end
- ct
->c_begin
;
1728 if (fstat (fd
, &st
) != NOTOK
)
1729 size
= (long) st
.st_size
;
1733 (*ct
->c_ceclosefnx
) (ct
);
1745 return init_encoding (ct
, openBase64
);
1750 openBase64 (CT ct
, char **file
)
1754 bool own_ct_fp
= false;
1755 char *cp
, *buffer
= NULL
;
1756 /* sbeck -- handle suffixes */
1758 CE ce
= &ct
->c_cefile
;
1759 unsigned char *decoded
;
1761 unsigned char digest
[16];
1764 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1769 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1770 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1776 if (*file
== NULL
) {
1779 ce
->ce_file
= mh_xstrdup(*file
);
1783 /* sbeck@cise.ufl.edu -- handle suffixes */
1785 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1786 if (ce
->ce_unlink
) {
1787 /* Create temporary file with filename extension. */
1788 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1789 die("unable to create temporary file in %s",
1793 ce
->ce_file
= add (cp
, ce
->ce_file
);
1795 } else if (*file
== NULL
) {
1797 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1798 die("unable to create temporary file in %s",
1801 ce
->ce_file
= mh_xstrdup(tempfile
);
1804 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1805 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1809 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1810 die("internal error(1)");
1812 buffer
= mh_xmalloc (len
+ 1);
1815 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1816 content_error (ct
->c_file
, ct
, "unable to open for reading");
1822 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1825 switch (cc
= read (fd
, cp
, len
)) {
1827 content_error (ct
->c_file
, ct
, "error reading from");
1831 content_error (NULL
, ct
, "premature eof");
1842 /* decodeBase64() requires null-terminated input. */
1845 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1846 ct
->c_digested
? digest
: NULL
) != OK
)
1851 unsigned char *decoded_p
= decoded
;
1852 for (i
= 0; i
< decoded_len
; ++i
) {
1853 putc (*decoded_p
++, ce
->ce_fp
);
1856 if (ferror (ce
->ce_fp
)) {
1857 content_error (ce
->ce_file
, ct
, "error writing to");
1861 if (ct
->c_digested
) {
1862 if (memcmp(digest
, ct
->c_digest
,
1864 content_error (NULL
, ct
,
1865 "content integrity suspect (digest mismatch) -- continuing");
1868 fprintf (stderr
, "content integrity confirmed\n");
1874 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1876 if (fflush (ce
->ce_fp
)) {
1877 content_error (ce
->ce_file
, ct
, "error writing to");
1881 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1884 *file
= ce
->ce_file
;
1890 return fileno (ce
->ce_fp
);
1897 free_encoding (ct
, 0);
1907 static char hex2nib
[0x80] = {
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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1913 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1914 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1915 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1916 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1917 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1918 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1919 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1920 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1921 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1922 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1923 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1930 return init_encoding (ct
, openQuoted
);
1935 openQuoted (CT ct
, char **file
)
1937 int cc
, digested
, len
, quoted
;
1938 bool own_ct_fp
= false;
1944 CE ce
= &ct
->c_cefile
;
1945 /* sbeck -- handle suffixes */
1950 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1955 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1956 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1962 if (*file
== NULL
) {
1965 ce
->ce_file
= mh_xstrdup(*file
);
1969 /* sbeck@cise.ufl.edu -- handle suffixes */
1971 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1972 if (ce
->ce_unlink
) {
1973 /* Create temporary file with filename extension. */
1974 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1975 die("unable to create temporary file in %s",
1979 ce
->ce_file
= add (cp
, ce
->ce_file
);
1981 } else if (*file
== NULL
) {
1983 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1984 die("unable to create temporary file in %s",
1987 ce
->ce_file
= mh_xstrdup(tempfile
);
1990 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1991 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1995 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1996 die("internal error(2)");
1999 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2000 content_error (ct
->c_file
, ct
, "unable to open for reading");
2006 if ((digested
= ct
->c_digested
))
2007 MD5Init (&mdContext
);
2011 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2013 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2014 content_error (NULL
, ct
, "premature eof");
2018 if ((cc
= gotlen
) > len
)
2022 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2023 if (!isspace ((unsigned char) *ep
))
2028 for (; cp
< ep
; cp
++) {
2030 /* in an escape sequence */
2032 /* at byte 1 of an escape sequence */
2033 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2034 /* next is byte 2 */
2037 /* at byte 2 of an escape sequence */
2039 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2040 putc (mask
, ce
->ce_fp
);
2042 MD5Update (&mdContext
, &mask
, 1);
2043 if (ferror (ce
->ce_fp
)) {
2044 content_error (ce
->ce_file
, ct
, "error writing to");
2047 /* finished escape sequence; next may be literal or a new
2048 * escape sequence */
2051 /* on to next byte */
2055 /* not in an escape sequence */
2057 /* starting an escape sequence, or invalid '='? */
2058 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2059 /* "=\n" soft line break, eat the \n */
2063 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2064 /* We don't have 2 bytes left, so this is an invalid
2065 * escape sequence; just show the raw bytes (below). */
2066 } else if (isxdigit ((unsigned char) cp
[1]) &&
2067 isxdigit ((unsigned char) cp
[2])) {
2068 /* Next 2 bytes are hex digits, making this a valid escape
2069 * sequence; let's decode it (above). */
2073 /* One or both of the next 2 is out of range, making this
2074 * an invalid escape sequence; just show the raw bytes
2078 /* Just show the raw byte. */
2079 putc (*cp
, ce
->ce_fp
);
2082 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2084 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2087 if (ferror (ce
->ce_fp
)) {
2088 content_error (ce
->ce_file
, ct
, "error writing to");
2094 content_error (NULL
, ct
,
2095 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2099 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2101 if (fflush (ce
->ce_fp
)) {
2102 content_error (ce
->ce_file
, ct
, "error writing to");
2107 unsigned char digest
[16];
2109 MD5Final (digest
, &mdContext
);
2110 if (memcmp(digest
, ct
->c_digest
,
2112 content_error (NULL
, ct
,
2113 "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
)
2159 bool own_ct_fp
= false;
2160 char buffer
[BUFSIZ
];
2161 /* sbeck -- handle suffixes */
2164 CE ce
= &ct
->c_cefile
;
2167 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2172 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2173 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2179 if (*file
== NULL
) {
2182 ce
->ce_file
= mh_xstrdup(*file
);
2186 /* sbeck@cise.ufl.edu -- handle suffixes */
2188 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2189 if (ce
->ce_unlink
) {
2190 /* Create temporary file with filename extension. */
2191 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2192 die("unable to create temporary file in %s",
2196 ce
->ce_file
= add (cp
, ce
->ce_file
);
2198 } else if (*file
== NULL
) {
2200 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2201 die("unable to create temporary file in %s",
2204 ce
->ce_file
= mh_xstrdup(tempfile
);
2207 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2208 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2212 if (ct
->c_type
== CT_MULTIPART
) {
2213 CI ci
= &ct
->c_ctinfo
;
2217 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2218 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2219 + 1 + strlen (ci
->ci_subtype
);
2220 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2223 fputs (buffer
, ce
->ce_fp
);
2227 if (ci
->ci_comment
) {
2228 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2229 fputs ("\n\t", ce
->ce_fp
);
2233 putc (' ', ce
->ce_fp
);
2236 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2239 fprintf (ce
->ce_fp
, "\n");
2241 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2243 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2245 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2246 fprintf (ce
->ce_fp
, "\n");
2249 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2250 die("internal error(3)");
2253 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2254 content_error (ct
->c_file
, ct
, "unable to open for reading");
2260 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2262 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2264 content_error (ct
->c_file
, ct
, "error reading from");
2268 content_error (NULL
, ct
, "premature eof");
2276 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2277 advise ("open7Bit", "fwrite");
2279 if (ferror (ce
->ce_fp
)) {
2280 content_error (ce
->ce_file
, ct
, "error writing to");
2285 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2287 if (fflush (ce
->ce_fp
)) {
2288 content_error (ce
->ce_file
, ct
, "error writing to");
2292 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2295 *file
= ce
->ce_file
;
2300 return fileno (ce
->ce_fp
);
2303 free_encoding (ct
, 0);
2317 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2319 char cachefile
[BUFSIZ
];
2322 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2327 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2328 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2334 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2335 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2336 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2337 ce
->ce_file
= mh_xstrdup(cachefile
);
2341 admonish (cachefile
, "unable to fopen for reading");
2344 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2348 *file
= ce
->ce_file
;
2349 *fd
= fileno (ce
->ce_fp
);
2360 return init_encoding (ct
, openFile
);
2365 openFile (CT ct
, char **file
)
2368 char cachefile
[BUFSIZ
];
2369 struct exbody
*e
= ct
->c_ctexbody
;
2370 CE ce
= &ct
->c_cefile
;
2372 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2384 content_error (NULL
, ct
, "missing name parameter");
2388 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2391 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2392 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2396 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2397 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2398 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2402 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2403 if ((fp
= fopen (cachefile
, "w"))) {
2405 char buffer
[BUFSIZ
];
2406 FILE *gp
= ce
->ce_fp
;
2408 fseek (gp
, 0L, SEEK_SET
);
2410 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2412 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2413 advise ("openFile", "fwrite");
2418 admonish (ce
->ce_file
, "error reading");
2419 (void) m_unlink (cachefile
);
2420 } else if (ferror (fp
)) {
2421 admonish (cachefile
, "error writing");
2422 (void) m_unlink (cachefile
);
2429 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2430 *file
= ce
->ce_file
;
2431 return fileno (ce
->ce_fp
);
2441 return init_encoding (ct
, openFTP
);
2446 openFTP (CT ct
, char **file
)
2452 char *bp
, *ftp
, *user
, *pass
;
2453 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2455 CE ce
= &ct
->c_cefile
;
2456 static char *username
= NULL
;
2457 static char *password
= NULL
;
2461 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2467 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2478 if (!e
->eb_name
|| !e
->eb_site
) {
2479 content_error (NULL
, ct
, "missing %s parameter",
2480 e
->eb_name
? "site": "name");
2484 /* Get the buffer ready to go */
2486 buflen
= sizeof(buffer
);
2489 * Construct the query message for user
2491 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2497 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2503 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2504 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2509 if (e
->eb_size
> 0) {
2510 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2515 snprintf (bp
, buflen
, "? ");
2518 * Now, check the answer
2520 if (!read_yes_or_no_if_tty (buffer
))
2525 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2529 ruserpass (e
->eb_site
, &username
, &password
, 0);
2534 ce
->ce_unlink
= (*file
== NULL
);
2536 cachefile
[0] = '\0';
2537 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2538 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2539 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2540 if (*file
== NULL
) {
2547 ce
->ce_file
= mh_xstrdup(*file
);
2549 ce
->ce_file
= mh_xstrdup(cachefile
);
2552 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2553 die("unable to create temporary file in %s",
2556 ce
->ce_file
= mh_xstrdup(tempfile
);
2559 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2560 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2569 vec
[vecp
++] = r1bindex (ftp
, '/');
2570 vec
[vecp
++] = e
->eb_site
;
2573 vec
[vecp
++] = e
->eb_dir
;
2574 vec
[vecp
++] = e
->eb_name
;
2575 vec
[vecp
++] = ce
->ce_file
,
2576 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2577 ? "ascii" : "binary";
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
);
2630 } else if (ferror (fp
)) {
2631 admonish (cachefile
, "error writing");
2632 (void) m_unlink (cachefile
);
2640 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2641 *file
= ce
->ce_file
;
2642 return fileno (ce
->ce_fp
);
2653 return init_encoding (ct
, openMail
);
2658 openMail (CT ct
, char **file
)
2660 int child_id
, fd
, vecp
;
2662 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2663 struct exbody
*e
= ct
->c_ctexbody
;
2664 CE ce
= &ct
->c_cefile
;
2666 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2677 if (!e
->eb_server
) {
2678 content_error (NULL
, ct
, "missing server parameter");
2682 /* Get buffer ready to go */
2684 buflen
= sizeof(buffer
);
2686 /* Now, construct query message */
2687 snprintf (bp
, buflen
, "Retrieve content");
2693 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2699 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2701 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2703 /* Now, check answer */
2704 if (!read_yes_or_no_if_tty (buffer
))
2708 vec
[vecp
++] = r1bindex (mailproc
, '/');
2709 vec
[vecp
++] = e
->eb_server
;
2710 vec
[vecp
++] = "-subject";
2711 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2712 vec
[vecp
++] = "-body";
2713 vec
[vecp
++] = e
->eb_body
;
2719 advise ("fork", "unable to");
2723 execvp (mailproc
, vec
);
2724 fprintf (stderr
, "unable to exec ");
2730 if (pidXwait (child_id
, NULL
) == OK
)
2731 inform("request sent");
2735 if (*file
== NULL
) {
2737 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2738 die("unable to create temporary file in %s",
2741 ce
->ce_file
= mh_xstrdup(tempfile
);
2744 ce
->ce_file
= mh_xstrdup(*file
);
2748 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2749 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2753 /* showproc is for mhshow and mhstore, though mhlist -debug
2754 * prints it, too. */
2755 free(ct
->c_showproc
);
2756 ct
->c_showproc
= mh_xstrdup("true");
2758 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2759 *file
= ce
->ce_file
;
2760 return fileno (ce
->ce_fp
);
2771 return init_encoding (ct
, openURL
);
2776 openURL (CT ct
, char **file
)
2778 struct exbody
*e
= ct
->c_ctexbody
;
2779 CE ce
= &ct
->c_cefile
;
2780 char *urlprog
, *program
;
2781 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2785 struct msgs_array args
= { 0, 0, NULL
};
2788 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2792 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2796 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2808 content_error(NULL
, ct
, "missing url parameter");
2812 ce
->ce_unlink
= (*file
== NULL
);
2814 cachefile
[0] = '\0';
2816 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2817 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2818 if (*file
== NULL
) {
2825 ce
->ce_file
= mh_xstrdup(*file
);
2827 ce
->ce_file
= mh_xstrdup(cachefile
);
2830 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2831 die("unable to create temporary file in %s",
2834 ce
->ce_file
= mh_xstrdup(tempfile
);
2837 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2838 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2842 switch (child_id
= fork()) {
2844 adios ("fork", "unable to");
2848 argsplit_msgarg(&args
, urlprog
, &program
);
2849 app_msgarg(&args
, e
->eb_url
);
2850 app_msgarg(&args
, NULL
);
2851 dup2(fileno(ce
->ce_fp
), 1);
2852 close(fileno(ce
->ce_fp
));
2853 execvp(program
, args
.msgs
);
2854 fprintf(stderr
, "Unable to exec ");
2860 if (pidXwait(child_id
, NULL
)) {
2868 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2873 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2874 if ((fp
= fopen(cachefile
, "w"))) {
2876 FILE *gp
= ce
->ce_fp
;
2878 fseeko(gp
, 0, SEEK_SET
);
2880 while ((cc
= fread(buffer
, sizeof(*buffer
),
2881 sizeof(buffer
), gp
)) > 0)
2882 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2883 advise ("openURL", "fwrite");
2889 admonish(ce
->ce_file
, "error reading");
2890 (void) m_unlink (cachefile
);
2897 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2898 *file
= ce
->ce_file
;
2899 return fileno(ce
->ce_fp
);
2904 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2905 * has to be base64 decoded.
2908 readDigest (CT ct
, char *cp
)
2910 unsigned char *digest
;
2913 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2914 const size_t maxlen
= sizeof ct
->c_digest
;
2916 if (strlen ((char *) digest
) <= maxlen
) {
2917 memcpy (ct
->c_digest
, digest
, maxlen
);
2922 fprintf (stderr
, "MD5 digest=");
2923 for (i
= 0; i
< maxlen
; ++i
) {
2924 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2926 fprintf (stderr
, "\n");
2932 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2933 (int) strlen ((char *) digest
));
2943 /* Multipart parts might have content before the first subpart and/or
2944 after the last subpart that hasn't been stored anywhere else, so do
2947 get_leftover_mp_content (CT ct
, int before
/* or after */)
2949 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2951 bool found_boundary
= false;
2957 char *content
= NULL
;
2959 if (! m
) return NOTOK
;
2962 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2964 /* Isolate the beginning of this part to the beginning of the
2965 first subpart and save any content between them. */
2966 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2967 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2968 boundary
= concat ("--", m
->mp_start
, NULL
);
2970 struct part
*last_subpart
= NULL
;
2971 struct part
*subpart
;
2973 /* Go to the last subpart to get its end position. */
2974 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2975 last_subpart
= subpart
;
2978 if (last_subpart
== NULL
) return NOTOK
;
2980 /* Isolate the end of the last subpart to the end of this part
2981 and save any content between them. */
2982 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2983 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2984 boundary
= concat ("--", m
->mp_stop
, NULL
);
2987 /* Back up by 1 to pick up the newline. */
2988 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2990 /* Don't look beyond beginning of first subpart (before) or
2991 next part (after). */
2992 if (read
> max
) bufp
[read
-max
] = '\0';
2995 if (! strcmp (bufp
, boundary
)) {
2996 found_boundary
= true;
2999 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3000 found_boundary
= true;
3005 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3007 char *old_content
= content
;
3008 content
= concat (content
, bufp
, NULL
);
3012 ? concat ("\n", bufp
, NULL
)
3013 : concat (bufp
, NULL
);
3018 if (found_boundary
|| read
> max
) break;
3020 if (read
> max
) break;
3024 /* Skip the newline if that's all there is. */
3028 /* Remove trailing newline, except at EOF. */
3029 if ((before
|| ! feof (ct
->c_fp
)) &&
3030 (cp
= content
+ strlen (content
)) > content
&&
3035 if (strlen (content
) > 1) {
3037 m
->mp_content_before
= content
;
3039 m
->mp_content_after
= content
;
3054 ct_type_str (int type
)
3057 case CT_APPLICATION
:
3058 return "application";
3074 return "unknown_type";
3080 ct_subtype_str (int type
, int subtype
)
3083 case CT_APPLICATION
:
3085 case APPLICATION_OCTETS
:
3087 case APPLICATION_POSTSCRIPT
:
3088 return "postscript";
3090 return "unknown_app_subtype";
3094 case MESSAGE_RFC822
:
3096 case MESSAGE_PARTIAL
:
3098 case MESSAGE_EXTERNAL
:
3101 return "unknown_msg_subtype";
3107 case MULTI_ALTERNATE
:
3108 return "alternative";
3111 case MULTI_PARALLEL
:
3116 return "unknown_multipart_subtype";
3127 return "unknown_text_subtype";
3130 return "unknown_type";
3136 ct_str_type (const char *type
)
3138 struct str2init
*s2i
;
3140 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3141 if (! strcasecmp (type
, s2i
->si_key
)) {
3145 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3154 ct_str_subtype (int type
, const char *subtype
)
3159 case CT_APPLICATION
:
3160 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3161 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3165 return kv
->kv_value
;
3167 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3168 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3172 return kv
->kv_value
;
3174 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3175 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3179 return kv
->kv_value
;
3181 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3182 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3186 return kv
->kv_value
;
3193 /* Find the content type and InitFunc for the CT. */
3194 const struct str2init
*
3195 get_ct_init (int type
)
3197 const struct str2init
*sp
;
3199 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3200 if (type
== sp
->si_val
) {
3209 ce_str (int encoding
)
3215 return "quoted-printable";
3231 /* Find the content type and InitFunc for the content encoding method. */
3232 const struct str2init
*
3233 get_ce_method (const char *method
)
3235 struct str2init
*sp
;
3237 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3238 if (! strcasecmp (method
, sp
->si_key
)) {
3247 * Parse a series of MIME attributes (or parameters) given a header as
3250 * Arguments include:
3252 * filename - Name of input file (for error messages)
3253 * fieldname - Name of field being processed
3254 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3255 * Updated to point to end of attributes when finished.
3256 * param_head - Pointer to head of parameter list
3257 * param_tail - Pointer to tail of parameter list
3258 * commentp - Pointer to header comment pointer (may be NULL)
3260 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3261 * DONE to indicate a benign error (minor parsing error, but the program
3266 parse_header_attrs (const char *filename
, const char *fieldname
,
3267 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3270 char *cp
= *header_attrp
;
3276 struct sectlist
*next
;
3282 struct sectlist
*sechead
;
3283 struct parmlist
*next
;
3284 } *pp
, *pp2
, *phead
= NULL
;
3286 while (*cp
== ';') {
3287 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3288 bool encoded
= false;
3289 bool partial
= false;
3290 int len
= 0, index
= 0;
3293 while (isspace ((unsigned char) *cp
))
3297 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3302 if (! suppress_extraneous_trailing_semicolon_warning
) {
3303 inform("extraneous trailing ';' in message %s's %s: "
3304 "parameter list", filename
, fieldname
);
3309 /* down case the attribute name */
3310 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3311 *dp
= tolower ((unsigned char) *dp
);
3313 for (up
= dp
; isspace ((unsigned char) *dp
);)
3315 if (dp
== cp
|| *dp
!= '=') {
3316 inform("invalid parameter in message %s's %s: field\n"
3317 " parameter %s (error detected at offset %ld)",
3318 filename
, fieldname
, cp
, (long)(dp
- cp
));
3323 * To handle RFC 2231, we have to deal with the following extensions:
3325 * name*=encoded-value
3326 * name*<N>=part-N-of-a-parameter-value
3327 * name*<N>*=encoded-part-N-of-a-parameter-value
3330 * If there's a * right before the equal sign, it's encoded.
3331 * If there's a * and one or more digits, then it's section N.
3333 * Remember we can have one or the other, or both. cp points to
3334 * beginning of name, up points past the last character in the
3338 for (vp
= cp
; vp
< up
; vp
++) {
3339 if (*vp
== '*' && vp
< up
- 1) {
3343 if (*vp
== '*' && vp
== up
- 1) {
3345 } else if (partial
) {
3346 if (isdigit((unsigned char) *vp
))
3347 index
= *vp
- '0' + index
* 10;
3349 inform("invalid parameter index in message %s's %s: field"
3350 "\n (parameter %s)", filename
, fieldname
, cp
);
3359 * Break out the parameter name and value sections and allocate
3363 nameptr
= mh_xmalloc(len
+ 1);
3364 strncpy(nameptr
, cp
, len
);
3365 nameptr
[len
] = '\0';
3367 for (dp
++; isspace ((unsigned char) *dp
);)
3372 * Single quotes delimit the character set and language tag.
3373 * They are required on the first section (or a complete
3378 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3384 charset
= mh_xmalloc(len
+ 1);
3385 strncpy(charset
, dp
, len
);
3386 charset
[len
] = '\0';
3392 inform("missing charset in message %s's %s: field\n"
3393 " (parameter %s)", filename
, fieldname
, nameptr
);
3399 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3406 lang
= mh_xmalloc(len
+ 1);
3407 strncpy(lang
, dp
, len
);
3414 inform("missing language tag in message %s's %s: field\n"
3415 " (parameter %s)", filename
, fieldname
, nameptr
);
3425 * At this point vp should be pointing at the beginning
3426 * of the encoded value/section. Continue until we reach
3427 * the end or get whitespace. But first, calculate the
3428 * length so we can allocate the correct buffer size.
3431 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3433 if (*(vp
+ 1) == '\0' ||
3434 !isxdigit((unsigned char) *(vp
+ 1)) ||
3435 *(vp
+ 2) == '\0' ||
3436 !isxdigit((unsigned char) *(vp
+ 2))) {
3437 inform("invalid encoded sequence in message %s's %s: field\n"
3438 " (parameter %s)", filename
, fieldname
, nameptr
);
3449 up
= valptr
= mh_xmalloc(len
+ 1);
3451 for (vp
= dp
; istoken(*vp
); vp
++) {
3453 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3464 * A "normal" string. If it's got a leading quote, then we
3465 * strip the quotes out. Otherwise go until we reach the end
3466 * or get whitespace. Note we scan it twice; once to get the
3467 * length, then the second time copies it into the destination
3474 for (cp
= dp
+ 1;;) {
3478 inform("invalid quoted-string in message %s's %s: field\n"
3479 " (parameter %s)", filename
, fieldname
, nameptr
);
3499 for (cp
= dp
; istoken (*cp
); cp
++) {
3504 valptr
= mh_xmalloc(len
+ 1);
3508 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3516 strncpy(valptr
, cp
= dp
, len
);
3524 * If 'partial' is set, we don't allocate a parameter now. We
3525 * put it on the parameter linked list to be reassembled later.
3527 * "phead" points to a list of all parameters we need to reassemble.
3528 * Each parameter has a list of sections. We insert the sections in
3533 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3534 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3549 * Insert this into the section linked list
3557 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3558 sp
->next
= pp
->sechead
;
3561 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3562 if (sp2
->index
== sp
->index
) {
3563 inform("duplicate index (%d) in message %s's %s: field"
3564 "\n (parameter %s)", sp
->index
, filename
,
3565 fieldname
, nameptr
);
3568 if (sp2
->index
< sp
->index
&&
3569 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3570 sp
->next
= sp2
->next
;
3577 inform("Internal error: cannot insert partial param "
3578 "in message %s's %s: field\n (parameter %s)",
3579 filename
, fieldname
, nameptr
);
3585 * Save our charset and lang tags.
3588 if (index
== 0 && encoded
) {
3590 pp
->charset
= charset
;
3595 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3596 pm
->pm_charset
= charset
;
3600 while (isspace ((unsigned char) *cp
))
3604 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3610 * Now that we're done, reassemble all of the partial parameters.
3613 for (pp
= phead
; pp
!= NULL
; ) {
3617 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3618 if (sp
->index
!= pindex
++) {
3619 inform("missing section %d for parameter in message "
3620 "%s's %s: field\n (parameter %s)", pindex
- 1,
3621 filename
, fieldname
, pp
->name
);
3627 p
= q
= mh_xmalloc(tlen
+ 1);
3628 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3629 memcpy(q
, sp
->value
, sp
->len
);
3639 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3640 pm
->pm_charset
= pp
->charset
;
3641 pm
->pm_lang
= pp
->lang
;
3652 * Return the charset for a particular content type.
3656 content_charset (CT ct
)
3658 char *ret_charset
= NULL
;
3660 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3662 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3667 * Create a string based on a list of output parameters. Assume that this
3668 * parameter string will be appended to an existing header, so start out
3669 * with the separator (;). Perform RFC 2231 encoding when necessary.
3673 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3675 char *paramout
= NULL
;
3676 char line
[CPERLIN
* 2], *q
;
3677 int curlen
, index
, cont
, encode
, i
;
3678 size_t valoff
, numchars
;
3680 while (params
!= NULL
) {
3686 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3689 if (strlen(params
->pm_name
) > CPERLIN
) {
3690 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3695 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3698 * Loop until we get a parameter that fits within a line. We
3699 * assume new lines start with a tab, so check our overflow based
3709 * At this point we're definitely continuing the line, so
3710 * be sure to include the parameter name and section index.
3713 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3714 params
->pm_name
, index
);
3717 * Both of these functions do a NUL termination
3721 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3722 numchars
, valoff
, index
);
3724 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3734 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3739 * "line" starts with a ;\n\t, so that doesn't count against
3740 * the length. But add 8 since it starts with a tab; that's
3741 * how we end up with 5.
3744 initialwidth
= strlen(line
) + 5;
3747 * At this point the line should be built, so add it to our
3748 * current output buffer.
3751 paramout
= add(line
, paramout
);
3755 * If this won't fit on the line, start a new one. Save room in
3756 * case we need a semicolon on the end
3759 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3771 * At this point, we're either finishing a continued parameter, or
3772 * we're working on a new one.
3776 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3777 params
->pm_name
, index
);
3779 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3784 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3785 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3787 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3788 strlen(params
->pm_value
+ valoff
), valoff
);
3795 paramout
= add(line
, paramout
);
3796 initialwidth
+= strlen(line
);
3798 params
= params
->pm_next
;
3802 *offsetout
= initialwidth
;
3808 * Calculate the size of a parameter.
3812 * pm - The parameter being output
3813 * index - If continuing the parameter, the index of the section
3815 * valueoff - The current offset into the parameter value that we're
3816 * working on (previous sections have consumed valueoff bytes).
3817 * encode - Set if we should perform encoding on this parameter section
3818 * (given that we're consuming bytesfit bytes).
3819 * cont - Set if the remaining data in value will not fit on a single
3820 * line and will need to be continued.
3821 * bytesfit - The number of bytes that we can consume from the parameter
3822 * value and still fit on a completely new line. The
3823 * calculation assumes the new line starts with a tab,
3824 * includes the parameter name and any encoding, and fits
3825 * within CPERLIN bytes. Will always be at least 1.
3829 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3832 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3833 size_t len
= 0, fit
= 0;
3834 int fitlimit
= 0, eightbit
, maxfit
;
3839 * Add up the length. First, start with the parameter name.
3842 len
= strlen(pm
->pm_name
);
3845 * Scan the parameter value and see if we need to do encoding for this
3849 eightbit
= contains8bit(start
, NULL
);
3852 * Determine if we need to encode this section. Encoding is necessary if:
3854 * - There are any 8-bit characters at all and we're on the first
3856 * - There are 8-bit characters within N bytes of our section start.
3857 * N is calculated based on the number of bytes it would take to
3858 * reach CPERLIN. Specifically:
3859 * 8 (starting tab) +
3860 * strlen(param name) +
3861 * 4 ('* for section marker, '=', opening/closing '"')
3863 * is the number of bytes used by everything that isn't part of the
3864 * value. So that gets subtracted from CPERLIN.
3867 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3868 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3869 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3873 len
++; /* Add in equal sign */
3877 * We're using maxfit as a marker for how many characters we can
3878 * fit into the line. Bump it by two because we're not using quotes
3885 * If we don't have a charset or language tag in this parameter,
3889 if (! pm
->pm_charset
) {
3890 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3891 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3892 die("8-bit characters in parameter \"%s\", but "
3893 "local character set is US-ASCII", pm
->pm_name
);
3896 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3898 len
++; /* For the encoding marker */
3901 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3906 * We know we definitely need to include an index. maxfit already
3907 * includes the section marker.
3909 len
+= strlen(indexchar
);
3911 for (p
= start
; *p
!= '\0'; p
++) {
3912 if (isparamencode(*p
)) {
3920 * Just so there's no confusion: maxfit is counting OUTPUT
3921 * characters (post-encoding). fit is counting INPUT characters.
3923 if (! fitlimit
&& maxfit
>= 0)
3925 else if (! fitlimit
)
3930 * Calculate the string length, but add room for quoting \
3931 * and " if necessary. Also account for quotes at beginning
3934 for (p
= start
; *p
!= '\0'; p
++) {
3945 if (! fitlimit
&& maxfit
>= 0)
3947 else if (! fitlimit
)
3964 * Output an encoded parameter string.
3968 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3969 size_t valueoff
, int index
)
3971 size_t outlen
= 0, n
;
3972 char *endptr
= output
+ len
, *p
;
3975 * First, output the marker for an encoded string.
3983 * If the index is 0, output the character set and language tag.
3984 * If theses were NULL, they should have already been filled in
3989 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3993 if (output
> endptr
) {
3994 inform("Internal error: parameter buffer overflow");
4000 * Copy over the value, encoding if necessary
4003 p
= pm
->pm_value
+ valueoff
;
4004 while (valuelen
-- > 0) {
4005 if (isparamencode(*p
)) {
4006 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4013 if (output
> endptr
) {
4014 inform("Internal error: parameter buffer overflow");
4025 * Output a "normal" parameter, without encoding. Be sure to escape
4026 * quotes and backslashes if necessary.
4030 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4034 char *endptr
= output
+ len
, *p
;
4040 p
= pm
->pm_value
+ valueoff
;
4042 while (valuelen
-- > 0) {
4053 if (output
> endptr
) {
4054 inform("Internal error: parameter buffer overflow");
4059 if (output
- 2 > endptr
) {
4060 inform("Internal error: parameter buffer overflow");
4071 * Add a parameter to the parameter linked list
4075 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4080 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4081 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4084 (*last
)->pm_next
= pm
;
4095 * Either replace a current parameter with a new value, or add the parameter
4096 * to the parameter linked list.
4100 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4104 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4105 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4107 * If nocopy is set, it's assumed that we own both name
4108 * and value. We don't need name, so we discard it now.
4113 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4118 return add_param(first
, last
, name
, value
, nocopy
);
4122 * Retrieve a parameter value from a parameter linked list. If the parameter
4123 * value needs converted to the local character set, do that now.
4127 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4129 while (first
!= NULL
) {
4130 if (strcasecmp(name
, first
->pm_name
) == 0) {
4132 return first
->pm_value
;
4133 return getcpy(get_param_value(first
, replace
));
4135 first
= first
->pm_next
;
4142 * Return a parameter value, converting to the local character set if
4147 get_param_value(PM pm
, char replace
)
4149 static char buffer
[4096]; /* I hope no parameters are larger */
4150 size_t bufsize
= sizeof(buffer
);
4155 ICONV_CONST
char *p
;
4156 #else /* HAVE_ICONV */
4158 #endif /* HAVE_ICONV */
4163 * If we don't have a character set indicated, it's assumed to be
4164 * US-ASCII. If it matches our character set, we don't need to convert
4168 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4169 strlen(pm
->pm_charset
))) {
4170 return pm
->pm_value
;
4174 * In this case, we need to convert. If we have iconv support, use
4175 * that. Otherwise, go through and simply replace every non-ASCII
4176 * character with the substitution character.
4181 bufsize
= sizeof(buffer
);
4182 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4184 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4185 if (cd
== (iconv_t
) -1) {
4189 inbytes
= strlen(pm
->pm_value
);
4193 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4194 if (errno
!= EILSEQ
) {
4199 * Reset shift state, substitute our character,
4200 * try to restart conversion.
4203 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4216 for (++p
, --inbytes
;
4217 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4236 #endif /* HAVE_ICONV */
4239 * Take everything non-ASCII and substitute the replacement character
4243 bufsize
= sizeof(buffer
);
4244 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4245 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))