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/getcpy.h"
10 #include "sbr/context_find.h"
11 #include "sbr/pidstatus.h"
12 #include "sbr/arglist.h"
13 #include "sbr/error.h"
19 #include "h/mhparse.h"
22 #include "h/mhcachesbr.h"
23 #include "sbr/m_mktemp.h"
27 #endif /* HAVE_ICONV */
28 #include "sbr/base64.h"
33 int checksw
= 0; /* check Content-MD5 field */
36 * These are for mhfixmsg to:
37 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
39 * 2) Suppress the warning about bogus multipart content, and report it.
40 * 3) Suppress the warning about extraneous trailing ';' in header parameter
43 bool skip_mp_cte_check
;
44 bool suppress_bogus_mp_content_warning
;
45 bool bogus_mp_content
;
46 bool suppress_extraneous_trailing_semicolon_warning
;
49 * By default, suppress warning about multiple MIME-Version header fields.
51 bool suppress_multiple_mime_version_warning
= true;
53 /* list of preferred type/subtype pairs, for -prefer */
54 mime_type_subtype mime_preference
[NPREFS
];
59 * Structures for TEXT messages
61 struct k2v SubText
[] = {
62 { "plain", TEXT_PLAIN
},
63 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
64 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
65 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
68 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
71 * Structures for MULTIPART messages
73 struct k2v SubMultiPart
[] = {
74 { "mixed", MULTI_MIXED
},
75 { "alternative", MULTI_ALTERNATE
},
76 { "digest", MULTI_DIGEST
},
77 { "parallel", MULTI_PARALLEL
},
78 { "related", MULTI_RELATED
},
79 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
83 * Structures for MESSAGE messages
85 struct k2v SubMessage
[] = {
86 { "rfc822", MESSAGE_RFC822
},
87 { "partial", MESSAGE_PARTIAL
},
88 { "external-body", MESSAGE_EXTERNAL
},
89 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
93 * Structure for APPLICATION messages
95 struct k2v SubApplication
[] = {
96 { "octet-stream", APPLICATION_OCTETS
},
97 { "postscript", APPLICATION_POSTSCRIPT
},
98 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
102 * Mapping of names of CTE types in mhbuild directives
104 static struct k2v EncodingType
[] = {
107 { "q-p", CE_QUOTED
},
108 { "quoted-printable", CE_QUOTED
},
109 { "b64", CE_BASE64
},
110 { "base64", CE_BASE64
},
118 static CT
get_content (FILE *, char *, int);
119 static int get_comment (const char *, const char *, char **, char **);
121 static int InitGeneric (CT
);
122 static int InitText (CT
);
123 static int InitMultiPart (CT
);
124 static void reverse_parts (CT
);
125 static void prefer_parts(CT ct
);
126 static int InitMessage (CT
);
127 static int InitApplication (CT
);
128 static int init_encoding (CT
, OpenCEFunc
);
129 static unsigned long size_encoding (CT
);
130 static int InitBase64 (CT
);
131 static int openBase64 (CT
, char **);
132 static int InitQuoted (CT
);
133 static int openQuoted (CT
, char **);
134 static int Init7Bit (CT
);
135 static int openExternal (CT
, CT
, CE
, char **, int *);
136 static int InitFile (CT
);
137 static int openFile (CT
, char **);
138 static int InitFTP (CT
);
139 static int openFTP (CT
, char **);
140 static int InitMail (CT
);
141 static int openMail (CT
, char **);
142 static int readDigest (CT
, char *);
143 static int get_leftover_mp_content (CT
, int);
144 static int InitURL (CT
);
145 static int openURL (CT
, char **);
146 static int parse_header_attrs (const char *, const char *, char **, PM
*,
148 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
149 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
150 static int get_dispo (char *, CT
, int);
152 struct str2init str2cts
[] = {
153 { "application", CT_APPLICATION
, InitApplication
},
154 { "audio", CT_AUDIO
, InitGeneric
},
155 { "image", CT_IMAGE
, InitGeneric
},
156 { "message", CT_MESSAGE
, InitMessage
},
157 { "multipart", CT_MULTIPART
, InitMultiPart
},
158 { "text", CT_TEXT
, InitText
},
159 { "video", CT_VIDEO
, InitGeneric
},
160 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
161 { NULL
, CT_UNKNOWN
, NULL
},
164 struct str2init str2ces
[] = {
165 { "base64", CE_BASE64
, InitBase64
},
166 { "quoted-printable", CE_QUOTED
, InitQuoted
},
167 { "8bit", CE_8BIT
, Init7Bit
},
168 { "7bit", CE_7BIT
, Init7Bit
},
169 { "binary", CE_BINARY
, Init7Bit
},
170 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
171 { NULL
, CE_UNKNOWN
, NULL
},
175 * NOTE WELL: si_key MUST NOT have value of NOTOK
177 * si_val is 1 if access method is anonymous.
179 struct str2init str2methods
[] = {
180 { "afs", 1, InitFile
},
181 { "anon-ftp", 1, InitFTP
},
182 { "ftp", 0, InitFTP
},
183 { "local-file", 0, InitFile
},
184 { "mail-server", 0, InitMail
},
185 { "url", 0, InitURL
},
191 * Main entry point for parsing a MIME message or file.
192 * It returns the Content structure for the top level
193 * entity in the file.
197 parse_mime (char *file
)
206 bogus_mp_content
= false;
209 * Check if file is actually standard input
211 if ((is_stdin
= !(strcmp (file
, "-")))) {
212 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
214 advise("mhparse", "unable to create temporary file in %s",
218 file
= mh_xstrdup(tfile
);
220 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
221 if (fwrite(buffer
, 1, n
, fp
) != n
) {
222 (void) m_unlink (file
);
223 advise (file
, "error copying to temporary file");
229 if (ferror (stdin
)) {
230 (void) m_unlink (file
);
231 advise ("stdin", "error reading");
235 (void) m_unlink (file
);
236 advise (file
, "error writing");
239 fseek (fp
, 0L, SEEK_SET
);
240 } else if (stat (file
, &statbuf
) == NOTOK
) {
241 advise (file
, "unable to stat");
243 } else if (S_ISDIR(statbuf
.st_mode
)) {
244 /* Don't try to parse a directory. */
245 inform("%s is a directory", file
);
247 } else if ((fp
= fopen (file
, "r")) == NULL
) {
248 advise (file
, "unable to read");
252 if (!(ct
= get_content (fp
, file
, 1))) {
254 (void) m_unlink (file
);
255 inform("unable to decode %s", file
);
260 ct
->c_unlink
= 1; /* temp file to remove */
264 if (ct
->c_end
== 0L) {
265 fseek (fp
, 0L, SEEK_END
);
266 ct
->c_end
= ftell (fp
);
269 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
281 * Main routine for reading/parsing the headers
282 * of a message content.
284 * toplevel = 1 # we are at the top level of the message
285 * toplevel = 0 # we are inside message type or multipart type
286 * # other than multipart/digest
287 * toplevel = -1 # we are inside multipart/digest
288 * NB: on failure we will fclose(in)!
292 get_content (FILE *in
, char *file
, int toplevel
)
295 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
299 m_getfld_state_t gstate
;
301 /* allocate the content structure */
304 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
305 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
308 * Parse the header fields for this
309 * content into a linked list.
311 gstate
= m_getfld_state_init(in
);
312 m_getfld_track_filepos2(&gstate
);
313 for (compnum
= 1;;) {
314 int bufsz
= sizeof buf
;
315 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
320 /* get copies of the buffers */
321 np
= mh_xstrdup(name
);
322 vp
= mh_xstrdup(buf
);
324 /* if necessary, get rest of field */
325 while (state
== FLDPLUS
) {
327 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
328 vp
= add (buf
, vp
); /* add to previous value */
331 /* Now add the header data to the list */
332 add_header (ct
, np
, vp
);
334 /* continue, to see if this isn't the last header field */
335 ct
->c_begin
= ftell (in
) + 1;
339 /* There are two cases. The unusual one is when there is no
340 * blank line between the headers and the body. This is
341 * indicated by the name of the header starting with `:'.
343 * For both cases, normal first, `1' is the desired c_begin
344 * file position for the start of the body, and `2' is the
345 * file position when buf is returned.
347 * f o o : b a r \n \n b o d y \n bufsz = 6
349 * f o o : b a r \n b o d y \n bufsz = 4
352 * For the normal case, bufsz includes the
353 * header-terminating `\n', even though it is not in buf,
354 * but bufsz isn't affected when it's missing in the unusual
356 if (name
[0] == ':') {
357 ct
->c_begin
= ftell(in
) - bufsz
;
359 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
364 ct
->c_begin
= ftell (in
);
369 die("message format error in component #%d", compnum
);
372 die("getfld() returned %d", state
);
375 /* break out of the loop */
378 m_getfld_state_destroy (&gstate
);
381 * Read the content headers. We will parse the
382 * MIME related header fields into their various
383 * structures and set internal flags related to
384 * content type/subtype, etc.
387 hp
= ct
->c_first_hf
; /* start at first header field */
389 /* Get MIME-Version field */
390 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
395 vrsn
= mh_xstrdup(FENDNULL(hp
->value
));
397 /* Now, cleanup this field */
400 while (isspace ((unsigned char) *cp
))
402 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
404 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
405 if (!isspace ((unsigned char) *dp
))
409 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
412 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
415 for (dp
= cp
; istoken (*dp
); dp
++)
419 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
422 inform("message %s has unknown value for %s: field (%s), continuing...",
423 ct
->c_file
, VRSN_FIELD
, cp
);
428 if (! suppress_multiple_mime_version_warning
)
429 inform("message %s has multiple %s: fields",
430 ct
->c_file
, VRSN_FIELD
);
434 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
435 /* Get Content-Type field */
436 struct str2init
*s2i
;
437 CI ci
= &ct
->c_ctinfo
;
439 /* Check if we've already seen a Content-Type header */
441 inform("message %s has multiple %s: fields",
442 ct
->c_file
, TYPE_FIELD
);
446 /* Parse the Content-Type field */
447 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
451 * Set the Init function and the internal
452 * flag for this content type.
454 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
455 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
457 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
459 ct
->c_type
= s2i
->si_val
;
460 ct
->c_ctinitfnx
= s2i
->si_init
;
462 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
463 /* Get Content-Transfer-Encoding field */
465 struct str2init
*s2i
;
468 * Check if we've already seen the
469 * Content-Transfer-Encoding field
472 inform("message %s has multiple %s: fields",
473 ct
->c_file
, ENCODING_FIELD
);
477 /* get copy of this field */
478 ct
->c_celine
= cp
= mh_xstrdup(FENDNULL(hp
->value
));
480 while (isspace ((unsigned char) *cp
))
482 for (dp
= cp
; istoken (*dp
); dp
++)
488 * Find the internal flag and Init function
489 * for this transfer encoding.
491 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
492 if (!strcasecmp (cp
, s2i
->si_key
))
494 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
497 ct
->c_encoding
= s2i
->si_val
;
499 /* Call the Init function for this encoding */
500 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
503 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
504 /* Get Content-MD5 field */
510 if (ct
->c_digested
) {
511 inform("message %s has multiple %s: fields",
512 ct
->c_file
, MD5_FIELD
);
516 ep
= cp
= mh_xstrdup(FENDNULL(hp
->value
)); /* get a copy */
518 while (isspace ((unsigned char) *cp
))
520 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
522 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
523 if (!isspace ((unsigned char) *dp
))
527 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
530 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
535 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
543 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
544 /* Get Content-ID field */
545 ct
->c_id
= add (hp
->value
, ct
->c_id
);
547 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
548 /* Get Content-Description field */
549 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
551 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
552 /* Get Content-Disposition field */
553 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
558 hp
= hp
->next
; /* next header field */
562 * Check if we saw a Content-Type field.
563 * If not, then assign a default value for
564 * it, and the Init function.
568 * If we are inside a multipart/digest message,
569 * so default type is message/rfc822
572 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
574 ct
->c_type
= CT_MESSAGE
;
575 ct
->c_ctinitfnx
= InitMessage
;
578 * Else default type is text/plain
580 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
582 ct
->c_type
= CT_TEXT
;
583 ct
->c_ctinitfnx
= InitText
;
587 /* Use default Transfer-Encoding, if necessary */
589 ct
->c_encoding
= CE_7BIT
;
602 * small routine to add header field to list
606 add_header (CT ct
, char *name
, char *value
)
610 /* allocate header field structure */
613 /* link data into header structure */
618 /* link header structure into the list */
619 if (ct
->c_first_hf
== NULL
) {
620 ct
->c_first_hf
= hp
; /* this is the first */
623 ct
->c_last_hf
->next
= hp
; /* add it to the end */
632 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
633 * directives. Fills in the information of the CTinfo structure.
636 get_ctinfo (char *cp
, CT ct
, int magic
)
645 /* store copy of Content-Type line */
646 cp
= ct
->c_ctline
= mh_xstrdup(FENDNULL(cp
));
648 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
651 /* change newlines to spaces */
652 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
655 /* trim trailing spaces */
656 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
657 if (!isspace ((unsigned char) *dp
))
662 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
664 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
665 &ci
->ci_comment
) == NOTOK
)
668 for (dp
= cp
; istoken (*dp
); dp
++)
672 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
677 inform("invalid %s: field in message %s (empty type)",
678 TYPE_FIELD
, ct
->c_file
);
681 to_lower(ci
->ci_type
);
683 while (isspace ((unsigned char) *cp
))
686 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
687 &ci
->ci_comment
) == NOTOK
)
692 ci
->ci_subtype
= mh_xstrdup("");
697 while (isspace ((unsigned char) *cp
))
700 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
701 &ci
->ci_comment
) == NOTOK
)
704 for (dp
= cp
; istoken (*dp
); dp
++)
708 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
712 if (!*ci
->ci_subtype
) {
713 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
714 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
717 to_lower(ci
->ci_subtype
);
720 while (isspace ((unsigned char) *cp
))
723 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
724 &ci
->ci_comment
) == NOTOK
)
727 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
728 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
729 &ci
->ci_comment
)) != OK
) {
730 return status
== NOTOK
? NOTOK
: OK
;
734 * Get any <Content-Id> given in buffer
736 if (magic
&& *cp
== '<') {
739 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
740 inform("invalid ID in message %s", ct
->c_file
);
746 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
752 while (isspace ((unsigned char) *cp
))
757 * Get any [Content-Description] given in buffer.
759 if (magic
&& *cp
== '[') {
761 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
765 inform("invalid description in message %s", ct
->c_file
);
773 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
779 while (isspace ((unsigned char) *cp
))
784 * Get any {Content-Disposition} given in buffer.
786 if (magic
&& *cp
== '{') {
788 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
792 inform("invalid disposition in message %s", ct
->c_file
);
800 if (get_dispo(cp
, ct
, 1) != OK
)
806 while (isspace ((unsigned char) *cp
))
811 * Get any extension directives (right now just the content transfer
812 * encoding, but maybe others) that we care about.
815 if (magic
&& *cp
== '*') {
817 * See if it's a CTE we match on
822 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
826 inform("invalid null transfer encoding specification");
833 ct
->c_reqencoding
= CE_UNKNOWN
;
835 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
836 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
837 ct
->c_reqencoding
= kv
->kv_value
;
842 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
843 inform("invalid CTE specification: \"%s\"", dp
);
847 while (isspace ((unsigned char) *cp
))
852 * Check if anything is left over
856 ci
->ci_magic
= mh_xstrdup(cp
);
858 /* If there is a Content-Disposition header and it doesn't
859 have a *filename=, extract it from the magic contents.
860 The r1bindex call skips any leading directory
862 if (ct
->c_dispo_type
&&
863 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
864 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
865 r1bindex(ci
->ci_magic
, '/'), 0);
869 inform("extraneous information in message %s's %s: field\n"
870 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
878 * Parse out a Content-Disposition header. A lot of this is cribbed from
882 get_dispo (char *cp
, CT ct
, int buildflag
)
884 char *dp
, *dispoheader
;
889 * Save the whole copy of the Content-Disposition header, unless we're
890 * processing a mhbuild directive. A NULL c_dispo will be a flag to
891 * mhbuild that the disposition header needs to be generated at that
895 dispoheader
= cp
= mh_xstrdup(FENDNULL(cp
));
897 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
900 /* change newlines to spaces */
901 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
904 /* trim trailing spaces */
905 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
906 if (!isspace ((unsigned char) *dp
))
911 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
913 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
919 for (dp
= cp
; istoken (*dp
); dp
++)
923 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
927 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
930 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
931 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
933 if (status
== NOTOK
) {
938 inform("extraneous information in message %s's %s: field\n (%s)",
939 ct
->c_file
, DISPO_FIELD
, cp
);
945 ct
->c_dispo
= dispoheader
;
952 get_comment (const char *filename
, const char *fieldname
, char **ap
,
957 char c
, buffer
[BUFSIZ
], *dp
;
967 inform("invalid comment in message %s's %s: field",
968 filename
, fieldname
);
973 if ((c
= *cp
++) == '\0')
996 if ((dp
= *commentp
)) {
997 *commentp
= concat (dp
, " ", buffer
, NULL
);
1000 *commentp
= mh_xstrdup(buffer
);
1004 while (isspace ((unsigned char) *cp
))
1015 * Handles content types audio, image, and video.
1016 * There's not much to do right here.
1024 return OK
; /* not much to do here */
1035 char buffer
[BUFSIZ
];
1040 CI ci
= &ct
->c_ctinfo
;
1042 /* check for missing subtype */
1043 if (!*ci
->ci_subtype
)
1044 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1047 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1049 /* allocate text character set structure */
1051 ct
->c_ctparams
= (void *) t
;
1053 /* scan for charset parameter */
1054 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1055 if (!strcasecmp (pm
->pm_name
, "charset"))
1058 /* check if content specified a character set */
1060 chset
= pm
->pm_value
;
1061 t
->tx_charset
= CHARSET_SPECIFIED
;
1063 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1067 * If we can not handle character set natively,
1068 * then check profile for string to modify the
1069 * terminal or display method.
1071 * termproc is for mhshow, though mhlist -debug prints it, too.
1073 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1074 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1075 if ((cp
= context_find (buffer
)))
1076 ct
->c_termproc
= mh_xstrdup(cp
);
1088 InitMultiPart (CT ct
)
1098 struct multipart
*m
;
1099 struct part
*part
, **next
;
1100 CI ci
= &ct
->c_ctinfo
;
1105 * The encoding for multipart messages must be either
1106 * 7bit, 8bit, or binary (per RFC 2045).
1108 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1109 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1110 /* Copy the Content-Transfer-Encoding header field body so we can
1111 remove any trailing whitespace and leading blanks from it. */
1112 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1114 bp
= cte
+ strlen (cte
) - 1;
1115 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1116 for (bp
= cte
; isblank((unsigned char)*bp
); ++bp
) continue;
1118 inform("\"%s/%s\" type in message %s must be encoded in\n"
1119 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1120 "mhfixmsg -fixcte can fix it, or\n"
1121 "manually edit the file and change the \"%s\"\n"
1122 "Content-Transfer-Encoding to one of those. For now, continuing...",
1123 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1130 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1133 * Check for "boundary" parameter, which is
1134 * required for multipart messages.
1137 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1138 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1144 /* complain if boundary parameter is missing */
1146 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1147 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1151 /* allocate primary structure for multipart info */
1153 ct
->c_ctparams
= (void *) m
;
1155 /* check if boundary parameter contains only whitespace characters */
1156 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1159 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1160 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1164 /* remove trailing whitespace from boundary parameter */
1165 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1166 if (!isspace ((unsigned char) *dp
))
1170 /* record boundary separators */
1171 m
->mp_start
= concat (bp
, "\n", NULL
);
1172 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1174 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1175 advise (ct
->c_file
, "unable to open for reading");
1179 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1181 next
= &m
->mp_parts
;
1185 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1190 if (bufp
[0] != '-' || bufp
[1] != '-')
1193 if (strcmp (bufp
+ 2, m
->mp_start
))
1198 next
= &part
->mp_next
;
1200 if (!(p
= get_content (fp
, ct
->c_file
,
1201 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1209 fseek (fp
, pos
, SEEK_SET
);
1212 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1216 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1217 if (p
->c_end
< p
->c_begin
)
1218 p
->c_begin
= p
->c_end
;
1223 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1228 if (! suppress_bogus_mp_content_warning
) {
1229 inform("bogus multipart content in message %s", ct
->c_file
);
1231 bogus_mp_content
= true;
1233 if (!inout
&& part
) {
1235 p
->c_end
= ct
->c_end
;
1237 if (p
->c_begin
>= p
->c_end
) {
1238 for (next
= &m
->mp_parts
; *next
!= part
;
1239 next
= &((*next
)->mp_next
))
1248 /* reverse the order of the parts for multipart/alternative */
1249 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1255 * label all subparts with part number, and
1256 * then initialize the content of the subpart.
1261 char partnam
[BUFSIZ
];
1264 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1265 pp
= partnam
+ strlen (partnam
);
1270 for (part
= m
->mp_parts
, partnum
= 1; part
;
1271 part
= part
->mp_next
, partnum
++) {
1274 sprintf (pp
, "%d", partnum
);
1275 p
->c_partno
= mh_xstrdup(partnam
);
1277 /* initialize the content of the subparts */
1278 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1287 get_leftover_mp_content (ct
, 1);
1288 get_leftover_mp_content (ct
, 0);
1298 * reverse the order of the parts of a multipart/alternative,
1299 * presumably to put the "most favored" alternative first, for
1300 * ease of choosing/displaying it later on. from a mail message on
1301 * nmh-workers, from kenh:
1302 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1303 * see code in mhn that did the same thing... According to the RCS
1304 * logs, that code was around from the initial checkin of mhn.c by
1305 * John Romine in 1992, which is as far back as we have."
1308 reverse_parts (CT ct
)
1310 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1314 /* Reverse the order of its parts by walking the mp_parts list
1315 and pushing each node to the front. */
1316 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1317 next
= part
->mp_next
;
1318 part
->mp_next
= m
->mp_parts
;
1324 move_preferred_part(CT ct
, mime_type_subtype
*pref
)
1326 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1327 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1331 /* move the matching part(s) to the head of the list: walk the
1332 * list of parts, move matching parts to a new list (maintaining
1333 * their order), and finally, concatenate the old list onto the
1340 head
->mp_next
= m
->mp_parts
;
1341 nhead
->mp_next
= NULL
;
1345 part
= head
->mp_next
;
1346 while (part
!= NULL
) {
1347 ci
= &part
->mp_part
->c_ctinfo
;
1348 if (!strcasecmp(ci
->ci_type
, pref
->type
) &&
1350 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1351 prev
->mp_next
= part
->mp_next
;
1352 part
->mp_next
= NULL
;
1353 ntail
->mp_next
= part
;
1355 part
= prev
->mp_next
;
1358 part
= prev
->mp_next
;
1361 ntail
->mp_next
= head
->mp_next
;
1362 m
->mp_parts
= nhead
->mp_next
;
1366 * move parts that match the user's preferences (-prefer) to the head
1367 * of the line. process preferences in reverse so first one given
1368 * ends up first in line
1374 for (i
= 0; i
< npreferred
; i
++)
1375 move_preferred_part(ct
, mime_preference
+ i
);
1380 /* parse_mime() arranges alternates in reverse (priority) order. This
1381 function can be used to reverse them back. This will put, for
1382 example, a text/plain part before a text/html part in a
1383 multipart/alternative part, for example, where it belongs. */
1385 reverse_alternative_parts (CT ct
)
1387 if (ct
->c_type
== CT_MULTIPART
) {
1388 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1391 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1395 /* And call recursively on each part of a multipart. */
1396 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1397 reverse_alternative_parts (part
->mp_part
);
1410 CI ci
= &ct
->c_ctinfo
;
1412 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1413 inform("\"%s/%s\" type in message %s should be encoded in "
1414 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1419 /* check for missing subtype */
1420 if (!*ci
->ci_subtype
)
1421 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1424 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1426 switch (ct
->c_subtype
) {
1427 case MESSAGE_RFC822
:
1430 case MESSAGE_PARTIAL
:
1436 ct
->c_ctparams
= (void *) p
;
1438 /* scan for parameters "id", "number", and "total" */
1439 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1440 if (!strcasecmp (pm
->pm_name
, "id")) {
1441 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1444 if (!strcasecmp (pm
->pm_name
, "number")) {
1445 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1446 || p
->pm_partno
< 1) {
1448 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1449 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1450 ct
->c_file
, TYPE_FIELD
);
1455 if (!strcasecmp (pm
->pm_name
, "total")) {
1456 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1465 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1466 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1467 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1473 case MESSAGE_EXTERNAL
:
1481 ct
->c_ctparams
= (void *) e
;
1484 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1485 advise (ct
->c_file
, "unable to open for reading");
1489 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1491 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1499 p
->c_ceopenfnx
= NULL
;
1500 if ((exresult
= params_external (ct
, 0)) != NOTOK
1501 && p
->c_ceopenfnx
== openMail
) {
1505 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1507 content_error (NULL
, ct
,
1508 "empty body for access-type=mail-server");
1512 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1513 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1515 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1517 adios ("failed", "fread");
1520 die("unexpected EOF from fread");
1523 bp
+= cc
, size
-= cc
;
1530 p
->c_end
= p
->c_begin
;
1535 if (exresult
== NOTOK
)
1537 if (e
->eb_flags
== NOTOK
)
1540 switch (p
->c_type
) {
1545 if (p
->c_subtype
!= MESSAGE_RFC822
)
1549 e
->eb_partno
= ct
->c_partno
;
1551 (*p
->c_ctinitfnx
) (p
);
1566 params_external (CT ct
, int composing
)
1569 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1570 CI ci
= &ct
->c_ctinfo
;
1572 ct
->c_ceopenfnx
= NULL
;
1573 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1574 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1575 struct str2init
*s2i
;
1576 CT p
= e
->eb_content
;
1578 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1579 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1582 e
->eb_access
= pm
->pm_value
;
1583 e
->eb_flags
= NOTOK
;
1584 p
->c_encoding
= CE_EXTERNAL
;
1587 e
->eb_access
= s2i
->si_key
;
1588 e
->eb_flags
= s2i
->si_val
;
1589 p
->c_encoding
= CE_EXTERNAL
;
1591 /* Call the Init function for this external type */
1592 if ((*s2i
->si_init
)(p
) == NOTOK
)
1596 if (!strcasecmp (pm
->pm_name
, "name")) {
1597 e
->eb_name
= pm
->pm_value
;
1600 if (!strcasecmp (pm
->pm_name
, "permission")) {
1601 e
->eb_permission
= pm
->pm_value
;
1604 if (!strcasecmp (pm
->pm_name
, "site")) {
1605 e
->eb_site
= pm
->pm_value
;
1608 if (!strcasecmp (pm
->pm_name
, "directory")) {
1609 e
->eb_dir
= pm
->pm_value
;
1612 if (!strcasecmp (pm
->pm_name
, "mode")) {
1613 e
->eb_mode
= pm
->pm_value
;
1616 if (!strcasecmp (pm
->pm_name
, "size")) {
1617 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1620 if (!strcasecmp (pm
->pm_name
, "server")) {
1621 e
->eb_server
= pm
->pm_value
;
1624 if (!strcasecmp (pm
->pm_name
, "subject")) {
1625 e
->eb_subject
= pm
->pm_value
;
1628 if (!strcasecmp (pm
->pm_name
, "url")) {
1630 * According to RFC 2017, we have to remove all whitespace from
1634 char *u
, *p
= pm
->pm_value
;
1635 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1637 for (; *p
!= '\0'; p
++) {
1638 if (! isspace((unsigned char) *p
))
1645 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1646 e
->eb_body
= getcpy (pm
->pm_value
);
1651 if (!e
->eb_access
) {
1652 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1653 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1666 InitApplication (CT ct
)
1668 CI ci
= &ct
->c_ctinfo
;
1671 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1678 * TRANSFER ENCODINGS
1682 init_encoding (CT ct
, OpenCEFunc openfnx
)
1684 ct
->c_ceopenfnx
= openfnx
;
1685 ct
->c_ceclosefnx
= close_encoding
;
1686 ct
->c_cesizefnx
= size_encoding
;
1693 close_encoding (CT ct
)
1695 CE ce
= &ct
->c_cefile
;
1704 static unsigned long
1705 size_encoding (CT ct
)
1710 CE ce
= &ct
->c_cefile
;
1713 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1714 return (long) st
.st_size
;
1717 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1718 return (long) st
.st_size
;
1722 if (ct
->c_encoding
== CE_EXTERNAL
)
1723 return ct
->c_end
- ct
->c_begin
;
1726 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1727 return ct
->c_end
- ct
->c_begin
;
1729 if (fstat (fd
, &st
) != NOTOK
)
1730 size
= (long) st
.st_size
;
1734 (*ct
->c_ceclosefnx
) (ct
);
1746 return init_encoding (ct
, openBase64
);
1751 openBase64 (CT ct
, char **file
)
1755 bool own_ct_fp
= false;
1756 char *cp
, *buffer
= NULL
;
1757 /* sbeck -- handle suffixes */
1759 CE ce
= &ct
->c_cefile
;
1760 unsigned char *decoded
;
1762 unsigned char digest
[16];
1765 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1770 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1771 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1777 if (*file
== NULL
) {
1780 ce
->ce_file
= mh_xstrdup(*file
);
1784 /* sbeck@cise.ufl.edu -- handle suffixes */
1786 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1787 if (ce
->ce_unlink
) {
1788 /* Create temporary file with filename extension. */
1789 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1790 die("unable to create temporary file in %s",
1794 ce
->ce_file
= add (cp
, ce
->ce_file
);
1796 } else if (*file
== NULL
) {
1798 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1799 die("unable to create temporary file in %s",
1802 ce
->ce_file
= mh_xstrdup(tempfile
);
1805 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1806 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1810 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1811 die("internal error(1)");
1813 buffer
= mh_xmalloc (len
+ 1);
1816 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1817 content_error (ct
->c_file
, ct
, "unable to open for reading");
1823 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1826 switch (cc
= read (fd
, cp
, len
)) {
1828 content_error (ct
->c_file
, ct
, "error reading from");
1832 content_error (NULL
, ct
, "premature eof");
1843 /* decodeBase64() requires null-terminated input. */
1846 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1847 ct
->c_digested
? digest
: NULL
) != OK
)
1852 unsigned char *decoded_p
= decoded
;
1853 for (i
= 0; i
< decoded_len
; ++i
) {
1854 putc (*decoded_p
++, ce
->ce_fp
);
1857 if (ferror (ce
->ce_fp
)) {
1858 content_error (ce
->ce_file
, ct
, "error writing to");
1862 if (ct
->c_digested
) {
1863 if (memcmp(digest
, ct
->c_digest
,
1865 content_error (NULL
, ct
,
1866 "content integrity suspect (digest mismatch) -- continuing");
1869 fprintf (stderr
, "content integrity confirmed\n");
1875 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1877 if (fflush (ce
->ce_fp
)) {
1878 content_error (ce
->ce_file
, ct
, "error writing to");
1882 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1885 *file
= ce
->ce_file
;
1891 return fileno (ce
->ce_fp
);
1898 free_encoding (ct
, 0);
1908 static char hex2nib
[0x80] = {
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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1915 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1916 0x08, 0x09, 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,
1921 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1922 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1923 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1924 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1931 return init_encoding (ct
, openQuoted
);
1936 openQuoted (CT ct
, char **file
)
1938 int cc
, digested
, len
, quoted
;
1939 bool own_ct_fp
= false;
1945 CE ce
= &ct
->c_cefile
;
1946 /* sbeck -- handle suffixes */
1951 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1956 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1957 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1963 if (*file
== NULL
) {
1966 ce
->ce_file
= mh_xstrdup(*file
);
1970 /* sbeck@cise.ufl.edu -- handle suffixes */
1972 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1973 if (ce
->ce_unlink
) {
1974 /* Create temporary file with filename extension. */
1975 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1976 die("unable to create temporary file in %s",
1980 ce
->ce_file
= add (cp
, ce
->ce_file
);
1982 } else if (*file
== NULL
) {
1984 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1985 die("unable to create temporary file in %s",
1988 ce
->ce_file
= mh_xstrdup(tempfile
);
1991 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1992 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1996 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1997 die("internal error(2)");
2000 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2001 content_error (ct
->c_file
, ct
, "unable to open for reading");
2007 if ((digested
= ct
->c_digested
))
2008 MD5Init (&mdContext
);
2012 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2014 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2015 content_error (NULL
, ct
, "premature eof");
2019 if ((cc
= gotlen
) > len
)
2023 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2024 if (!isspace ((unsigned char) *ep
))
2029 for (; cp
< ep
; cp
++) {
2031 /* in an escape sequence */
2033 /* at byte 1 of an escape sequence */
2034 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2035 /* next is byte 2 */
2038 /* at byte 2 of an escape sequence */
2040 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2041 putc (mask
, ce
->ce_fp
);
2043 MD5Update (&mdContext
, &mask
, 1);
2044 if (ferror (ce
->ce_fp
)) {
2045 content_error (ce
->ce_file
, ct
, "error writing to");
2048 /* finished escape sequence; next may be literal or a new
2049 * escape sequence */
2052 /* on to next byte */
2056 /* not in an escape sequence */
2058 /* starting an escape sequence, or invalid '='? */
2059 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2060 /* "=\n" soft line break, eat the \n */
2064 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2065 /* We don't have 2 bytes left, so this is an invalid
2066 * escape sequence; just show the raw bytes (below). */
2067 } else if (isxdigit ((unsigned char) cp
[1]) &&
2068 isxdigit ((unsigned char) cp
[2])) {
2069 /* Next 2 bytes are hex digits, making this a valid escape
2070 * sequence; let's decode it (above). */
2074 /* One or both of the next 2 is out of range, making this
2075 * an invalid escape sequence; just show the raw bytes
2079 /* Just show the raw byte. */
2080 putc (*cp
, ce
->ce_fp
);
2083 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2085 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2088 if (ferror (ce
->ce_fp
)) {
2089 content_error (ce
->ce_file
, ct
, "error writing to");
2095 content_error (NULL
, ct
,
2096 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2100 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2102 if (fflush (ce
->ce_fp
)) {
2103 content_error (ce
->ce_file
, ct
, "error writing to");
2108 unsigned char digest
[16];
2110 MD5Final (digest
, &mdContext
);
2111 if (memcmp(digest
, ct
->c_digest
,
2113 content_error (NULL
, ct
,
2114 "content integrity suspect (digest mismatch) -- continuing");
2116 fprintf (stderr
, "content integrity confirmed\n");
2119 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2122 *file
= ce
->ce_file
;
2128 return fileno (ce
->ce_fp
);
2131 free_encoding (ct
, 0);
2148 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2151 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2157 open7Bit (CT ct
, char **file
)
2160 bool own_ct_fp
= false;
2161 char buffer
[BUFSIZ
];
2162 /* sbeck -- handle suffixes */
2165 CE ce
= &ct
->c_cefile
;
2168 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2173 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2174 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2180 if (*file
== NULL
) {
2183 ce
->ce_file
= mh_xstrdup(*file
);
2187 /* sbeck@cise.ufl.edu -- handle suffixes */
2189 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2190 if (ce
->ce_unlink
) {
2191 /* Create temporary file with filename extension. */
2192 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2193 die("unable to create temporary file in %s",
2197 ce
->ce_file
= add (cp
, ce
->ce_file
);
2199 } else if (*file
== NULL
) {
2201 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2202 die("unable to create temporary file in %s",
2205 ce
->ce_file
= mh_xstrdup(tempfile
);
2208 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2209 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2213 if (ct
->c_type
== CT_MULTIPART
) {
2214 CI ci
= &ct
->c_ctinfo
;
2218 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2219 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2220 + 1 + strlen (ci
->ci_subtype
);
2221 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2224 fputs (buffer
, ce
->ce_fp
);
2228 if (ci
->ci_comment
) {
2229 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2230 fputs ("\n\t", ce
->ce_fp
);
2234 putc (' ', ce
->ce_fp
);
2237 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2240 fprintf (ce
->ce_fp
, "\n");
2242 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2244 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2246 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2247 fprintf (ce
->ce_fp
, "\n");
2250 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2251 die("internal error(3)");
2254 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2255 content_error (ct
->c_file
, ct
, "unable to open for reading");
2261 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2263 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2265 content_error (ct
->c_file
, ct
, "error reading from");
2269 content_error (NULL
, ct
, "premature eof");
2277 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2278 advise ("open7Bit", "fwrite");
2280 if (ferror (ce
->ce_fp
)) {
2281 content_error (ce
->ce_file
, ct
, "error writing to");
2286 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2288 if (fflush (ce
->ce_fp
)) {
2289 content_error (ce
->ce_file
, ct
, "error writing to");
2293 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2296 *file
= ce
->ce_file
;
2301 return fileno (ce
->ce_fp
);
2304 free_encoding (ct
, 0);
2318 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2320 char cachefile
[BUFSIZ
];
2323 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2328 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2329 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2335 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2336 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2337 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2338 ce
->ce_file
= mh_xstrdup(cachefile
);
2342 admonish (cachefile
, "unable to fopen for reading");
2345 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2349 *file
= ce
->ce_file
;
2350 *fd
= fileno (ce
->ce_fp
);
2361 return init_encoding (ct
, openFile
);
2366 openFile (CT ct
, char **file
)
2369 char cachefile
[BUFSIZ
];
2370 struct exbody
*e
= ct
->c_ctexbody
;
2371 CE ce
= &ct
->c_cefile
;
2373 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2385 content_error (NULL
, ct
, "missing name parameter");
2389 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2392 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2393 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2397 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2398 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2399 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2403 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2404 if ((fp
= fopen (cachefile
, "w"))) {
2406 char buffer
[BUFSIZ
];
2407 FILE *gp
= ce
->ce_fp
;
2409 fseek (gp
, 0L, SEEK_SET
);
2411 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2413 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2414 advise ("openFile", "fwrite");
2419 admonish (ce
->ce_file
, "error reading");
2420 (void) m_unlink (cachefile
);
2421 } else if (ferror (fp
)) {
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
)
2453 char *bp
, *ftp
, *user
, *pass
;
2454 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2456 CE ce
= &ct
->c_cefile
;
2457 static char *username
= NULL
;
2458 static char *password
= NULL
;
2462 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2468 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2479 if (!e
->eb_name
|| !e
->eb_site
) {
2480 content_error (NULL
, ct
, "missing %s parameter",
2481 e
->eb_name
? "site": "name");
2485 /* Get the buffer ready to go */
2487 buflen
= sizeof(buffer
);
2490 * Construct the query message for user
2492 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2498 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2504 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2505 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2510 if (e
->eb_size
> 0) {
2511 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2516 snprintf (bp
, buflen
, "? ");
2519 * Now, check the answer
2521 if (!read_yes_or_no_if_tty (buffer
))
2526 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2530 ruserpass (e
->eb_site
, &username
, &password
, 0);
2535 ce
->ce_unlink
= (*file
== NULL
);
2537 cachefile
[0] = '\0';
2538 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2539 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2540 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2541 if (*file
== NULL
) {
2548 ce
->ce_file
= mh_xstrdup(*file
);
2550 ce
->ce_file
= mh_xstrdup(cachefile
);
2553 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2554 die("unable to create temporary file in %s",
2557 ce
->ce_file
= mh_xstrdup(tempfile
);
2560 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2561 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2570 vec
[vecp
++] = r1bindex (ftp
, '/');
2571 vec
[vecp
++] = e
->eb_site
;
2574 vec
[vecp
++] = e
->eb_dir
;
2575 vec
[vecp
++] = e
->eb_name
;
2576 vec
[vecp
++] = ce
->ce_file
,
2577 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2578 ? "ascii" : "binary";
2586 adios ("fork", "unable to");
2590 close (fileno (ce
->ce_fp
));
2592 fprintf (stderr
, "unable to exec ");
2598 if (pidXwait (child_id
, NULL
)) {
2599 username
= password
= NULL
;
2609 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2614 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2615 if ((fp
= fopen (cachefile
, "w"))) {
2617 FILE *gp
= ce
->ce_fp
;
2619 fseek (gp
, 0L, SEEK_SET
);
2621 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2623 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2624 advise ("openFTP", "fwrite");
2629 admonish (ce
->ce_file
, "error reading");
2630 (void) m_unlink (cachefile
);
2631 } else if (ferror (fp
)) {
2632 admonish (cachefile
, "error writing");
2633 (void) m_unlink (cachefile
);
2641 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2642 *file
= ce
->ce_file
;
2643 return fileno (ce
->ce_fp
);
2654 return init_encoding (ct
, openMail
);
2659 openMail (CT ct
, char **file
)
2661 int child_id
, fd
, vecp
;
2663 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2664 struct exbody
*e
= ct
->c_ctexbody
;
2665 CE ce
= &ct
->c_cefile
;
2667 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2678 if (!e
->eb_server
) {
2679 content_error (NULL
, ct
, "missing server parameter");
2683 /* Get buffer ready to go */
2685 buflen
= sizeof(buffer
);
2687 /* Now, construct query message */
2688 snprintf (bp
, buflen
, "Retrieve content");
2694 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2700 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2702 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2704 /* Now, check answer */
2705 if (!read_yes_or_no_if_tty (buffer
))
2709 vec
[vecp
++] = r1bindex (mailproc
, '/');
2710 vec
[vecp
++] = e
->eb_server
;
2711 vec
[vecp
++] = "-subject";
2712 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2713 vec
[vecp
++] = "-body";
2714 vec
[vecp
++] = e
->eb_body
;
2720 advise ("fork", "unable to");
2724 execvp (mailproc
, vec
);
2725 fprintf (stderr
, "unable to exec ");
2731 if (pidXwait (child_id
, NULL
) == OK
)
2732 inform("request sent");
2736 if (*file
== NULL
) {
2738 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2739 die("unable to create temporary file in %s",
2742 ce
->ce_file
= mh_xstrdup(tempfile
);
2745 ce
->ce_file
= mh_xstrdup(*file
);
2749 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2750 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2754 /* showproc is for mhshow and mhstore, though mhlist -debug
2755 * prints it, too. */
2756 free(ct
->c_showproc
);
2757 ct
->c_showproc
= mh_xstrdup("true");
2759 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2760 *file
= ce
->ce_file
;
2761 return fileno (ce
->ce_fp
);
2772 return init_encoding (ct
, openURL
);
2777 openURL (CT ct
, char **file
)
2779 struct exbody
*e
= ct
->c_ctexbody
;
2780 CE ce
= &ct
->c_cefile
;
2781 char *urlprog
, *program
;
2782 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
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 die("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
;
2900 return fileno(ce
->ce_fp
);
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 bool found_boundary
= false;
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
)) {
2997 found_boundary
= true;
3000 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3001 found_boundary
= true;
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
)
3058 case CT_APPLICATION
:
3059 return "application";
3075 return "unknown_type";
3081 ct_subtype_str (int type
, int subtype
)
3084 case CT_APPLICATION
:
3086 case APPLICATION_OCTETS
:
3088 case APPLICATION_POSTSCRIPT
:
3089 return "postscript";
3091 return "unknown_app_subtype";
3095 case MESSAGE_RFC822
:
3097 case MESSAGE_PARTIAL
:
3099 case MESSAGE_EXTERNAL
:
3102 return "unknown_msg_subtype";
3108 case MULTI_ALTERNATE
:
3109 return "alternative";
3112 case MULTI_PARALLEL
:
3117 return "unknown_multipart_subtype";
3128 return "unknown_text_subtype";
3131 return "unknown_type";
3137 ct_str_type (const char *type
)
3139 struct str2init
*s2i
;
3141 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3142 if (! strcasecmp (type
, s2i
->si_key
)) {
3146 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3155 ct_str_subtype (int type
, const char *subtype
)
3160 case CT_APPLICATION
:
3161 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3162 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3166 return kv
->kv_value
;
3168 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3169 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3173 return kv
->kv_value
;
3175 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3176 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3180 return kv
->kv_value
;
3182 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3183 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3187 return kv
->kv_value
;
3194 /* Find the content type and InitFunc for the CT. */
3195 const struct str2init
*
3196 get_ct_init (int type
)
3198 const struct str2init
*sp
;
3200 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3201 if (type
== sp
->si_val
) {
3210 ce_str (int encoding
)
3216 return "quoted-printable";
3232 /* Find the content type and InitFunc for the content encoding method. */
3233 const struct str2init
*
3234 get_ce_method (const char *method
)
3236 struct str2init
*sp
;
3238 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3239 if (! strcasecmp (method
, sp
->si_key
)) {
3248 * Parse a series of MIME attributes (or parameters) given a header as
3251 * Arguments include:
3253 * filename - Name of input file (for error messages)
3254 * fieldname - Name of field being processed
3255 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3256 * Updated to point to end of attributes when finished.
3257 * param_head - Pointer to head of parameter list
3258 * param_tail - Pointer to tail of parameter list
3259 * commentp - Pointer to header comment pointer (may be NULL)
3261 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3262 * DONE to indicate a benign error (minor parsing error, but the program
3267 parse_header_attrs (const char *filename
, const char *fieldname
,
3268 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3271 char *cp
= *header_attrp
;
3277 struct sectlist
*next
;
3283 struct sectlist
*sechead
;
3284 struct parmlist
*next
;
3285 } *pp
, *pp2
, *phead
= NULL
;
3287 while (*cp
== ';') {
3288 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3289 bool encoded
= false;
3290 bool partial
= false;
3291 int len
= 0, index
= 0;
3294 while (isspace ((unsigned char) *cp
))
3298 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3303 if (! suppress_extraneous_trailing_semicolon_warning
) {
3304 inform("extraneous trailing ';' in message %s's %s: "
3305 "parameter list", filename
, fieldname
);
3310 /* down case the attribute name */
3311 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3312 *dp
= tolower ((unsigned char) *dp
);
3314 for (up
= dp
; isspace ((unsigned char) *dp
);)
3316 if (dp
== cp
|| *dp
!= '=') {
3317 inform("invalid parameter in message %s's %s: field\n"
3318 " parameter %s (error detected at offset %ld)",
3319 filename
, fieldname
, cp
, (long)(dp
- cp
));
3324 * To handle RFC 2231, we have to deal with the following extensions:
3326 * name*=encoded-value
3327 * name*<N>=part-N-of-a-parameter-value
3328 * name*<N>*=encoded-part-N-of-a-parameter-value
3331 * If there's a * right before the equal sign, it's encoded.
3332 * If there's a * and one or more digits, then it's section N.
3334 * Remember we can have one or the other, or both. cp points to
3335 * beginning of name, up points past the last character in the
3339 for (vp
= cp
; vp
< up
; vp
++) {
3340 if (*vp
== '*' && vp
< up
- 1) {
3344 if (*vp
== '*' && vp
== up
- 1) {
3346 } else if (partial
) {
3347 if (isdigit((unsigned char) *vp
))
3348 index
= *vp
- '0' + index
* 10;
3350 inform("invalid parameter index in message %s's %s: field"
3351 "\n (parameter %s)", filename
, fieldname
, cp
);
3360 * Break out the parameter name and value sections and allocate
3364 nameptr
= mh_xmalloc(len
+ 1);
3365 strncpy(nameptr
, cp
, len
);
3366 nameptr
[len
] = '\0';
3368 for (dp
++; isspace ((unsigned char) *dp
);)
3373 * Single quotes delimit the character set and language tag.
3374 * They are required on the first section (or a complete
3379 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3385 charset
= mh_xmalloc(len
+ 1);
3386 strncpy(charset
, dp
, len
);
3387 charset
[len
] = '\0';
3393 inform("missing charset in message %s's %s: field\n"
3394 " (parameter %s)", filename
, fieldname
, nameptr
);
3400 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3407 lang
= mh_xmalloc(len
+ 1);
3408 strncpy(lang
, dp
, len
);
3415 inform("missing language tag in message %s's %s: field\n"
3416 " (parameter %s)", filename
, fieldname
, nameptr
);
3426 * At this point vp should be pointing at the beginning
3427 * of the encoded value/section. Continue until we reach
3428 * the end or get whitespace. But first, calculate the
3429 * length so we can allocate the correct buffer size.
3432 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3434 if (*(vp
+ 1) == '\0' ||
3435 !isxdigit((unsigned char) *(vp
+ 1)) ||
3436 *(vp
+ 2) == '\0' ||
3437 !isxdigit((unsigned char) *(vp
+ 2))) {
3438 inform("invalid encoded sequence in message %s's %s: field\n"
3439 " (parameter %s)", filename
, fieldname
, nameptr
);
3450 up
= valptr
= mh_xmalloc(len
+ 1);
3452 for (vp
= dp
; istoken(*vp
); vp
++) {
3454 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3465 * A "normal" string. If it's got a leading quote, then we
3466 * strip the quotes out. Otherwise go until we reach the end
3467 * or get whitespace. Note we scan it twice; once to get the
3468 * length, then the second time copies it into the destination
3475 for (cp
= dp
+ 1;;) {
3479 inform("invalid quoted-string in message %s's %s: field\n"
3480 " (parameter %s)", filename
, fieldname
, nameptr
);
3500 for (cp
= dp
; istoken (*cp
); cp
++) {
3505 valptr
= mh_xmalloc(len
+ 1);
3509 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3517 strncpy(valptr
, cp
= dp
, len
);
3525 * If 'partial' is set, we don't allocate a parameter now. We
3526 * put it on the parameter linked list to be reassembled later.
3528 * "phead" points to a list of all parameters we need to reassemble.
3529 * Each parameter has a list of sections. We insert the sections in
3534 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3535 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3550 * Insert this into the section linked list
3558 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3559 sp
->next
= pp
->sechead
;
3562 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3563 if (sp2
->index
== sp
->index
) {
3564 inform("duplicate index (%d) in message %s's %s: field"
3565 "\n (parameter %s)", sp
->index
, filename
,
3566 fieldname
, nameptr
);
3569 if (sp2
->index
< sp
->index
&&
3570 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3571 sp
->next
= sp2
->next
;
3578 inform("Internal error: cannot insert partial param "
3579 "in message %s's %s: field\n (parameter %s)",
3580 filename
, fieldname
, nameptr
);
3586 * Save our charset and lang tags.
3589 if (index
== 0 && encoded
) {
3591 pp
->charset
= charset
;
3596 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3597 pm
->pm_charset
= charset
;
3601 while (isspace ((unsigned char) *cp
))
3605 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3611 * Now that we're done, reassemble all of the partial parameters.
3614 for (pp
= phead
; pp
!= NULL
; ) {
3618 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3619 if (sp
->index
!= pindex
++) {
3620 inform("missing section %d for parameter in message "
3621 "%s's %s: field\n (parameter %s)", pindex
- 1,
3622 filename
, fieldname
, pp
->name
);
3628 p
= q
= mh_xmalloc(tlen
+ 1);
3629 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3630 memcpy(q
, sp
->value
, sp
->len
);
3640 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3641 pm
->pm_charset
= pp
->charset
;
3642 pm
->pm_lang
= pp
->lang
;
3653 * Return the charset for a particular content type.
3657 content_charset (CT ct
)
3659 char *ret_charset
= NULL
;
3661 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3663 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3668 * Create a string based on a list of output parameters. Assume that this
3669 * parameter string will be appended to an existing header, so start out
3670 * with the separator (;). Perform RFC 2231 encoding when necessary.
3674 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3676 char *paramout
= NULL
;
3677 char line
[CPERLIN
* 2], *q
;
3678 int curlen
, index
, cont
, encode
, i
;
3679 size_t valoff
, numchars
;
3681 while (params
!= NULL
) {
3687 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3690 if (strlen(params
->pm_name
) > CPERLIN
) {
3691 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3696 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3699 * Loop until we get a parameter that fits within a line. We
3700 * assume new lines start with a tab, so check our overflow based
3710 * At this point we're definitely continuing the line, so
3711 * be sure to include the parameter name and section index.
3714 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3715 params
->pm_name
, index
);
3718 * Both of these functions do a NUL termination
3722 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3723 numchars
, valoff
, index
);
3725 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3735 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3740 * "line" starts with a ;\n\t, so that doesn't count against
3741 * the length. But add 8 since it starts with a tab; that's
3742 * how we end up with 5.
3745 initialwidth
= strlen(line
) + 5;
3748 * At this point the line should be built, so add it to our
3749 * current output buffer.
3752 paramout
= add(line
, paramout
);
3756 * If this won't fit on the line, start a new one. Save room in
3757 * case we need a semicolon on the end
3760 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3772 * At this point, we're either finishing a continued parameter, or
3773 * we're working on a new one.
3777 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3778 params
->pm_name
, index
);
3780 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3785 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3786 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3788 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3789 strlen(params
->pm_value
+ valoff
), valoff
);
3796 paramout
= add(line
, paramout
);
3797 initialwidth
+= strlen(line
);
3799 params
= params
->pm_next
;
3803 *offsetout
= initialwidth
;
3809 * Calculate the size of a parameter.
3813 * pm - The parameter being output
3814 * index - If continuing the parameter, the index of the section
3816 * valueoff - The current offset into the parameter value that we're
3817 * working on (previous sections have consumed valueoff bytes).
3818 * encode - Set if we should perform encoding on this parameter section
3819 * (given that we're consuming bytesfit bytes).
3820 * cont - Set if the remaining data in value will not fit on a single
3821 * line and will need to be continued.
3822 * bytesfit - The number of bytes that we can consume from the parameter
3823 * value and still fit on a completely new line. The
3824 * calculation assumes the new line starts with a tab,
3825 * includes the parameter name and any encoding, and fits
3826 * within CPERLIN bytes. Will always be at least 1.
3830 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3833 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3834 size_t len
= 0, fit
= 0;
3835 int fitlimit
= 0, eightbit
, maxfit
;
3840 * Add up the length. First, start with the parameter name.
3843 len
= strlen(pm
->pm_name
);
3846 * Scan the parameter value and see if we need to do encoding for this
3850 eightbit
= contains8bit(start
, NULL
);
3853 * Determine if we need to encode this section. Encoding is necessary if:
3855 * - There are any 8-bit characters at all and we're on the first
3857 * - There are 8-bit characters within N bytes of our section start.
3858 * N is calculated based on the number of bytes it would take to
3859 * reach CPERLIN. Specifically:
3860 * 8 (starting tab) +
3861 * strlen(param name) +
3862 * 4 ('* for section marker, '=', opening/closing '"')
3864 * is the number of bytes used by everything that isn't part of the
3865 * value. So that gets subtracted from CPERLIN.
3868 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3869 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3870 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3874 len
++; /* Add in equal sign */
3878 * We're using maxfit as a marker for how many characters we can
3879 * fit into the line. Bump it by two because we're not using quotes
3886 * If we don't have a charset or language tag in this parameter,
3890 if (! pm
->pm_charset
) {
3891 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3892 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3893 die("8-bit characters in parameter \"%s\", but "
3894 "local character set is US-ASCII", pm
->pm_name
);
3897 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3899 len
++; /* For the encoding marker */
3902 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3907 * We know we definitely need to include an index. maxfit already
3908 * includes the section marker.
3910 len
+= strlen(indexchar
);
3912 for (p
= start
; *p
!= '\0'; p
++) {
3913 if (isparamencode(*p
)) {
3921 * Just so there's no confusion: maxfit is counting OUTPUT
3922 * characters (post-encoding). fit is counting INPUT characters.
3924 if (! fitlimit
&& maxfit
>= 0)
3926 else if (! fitlimit
)
3931 * Calculate the string length, but add room for quoting \
3932 * and " if necessary. Also account for quotes at beginning
3935 for (p
= start
; *p
!= '\0'; p
++) {
3946 if (! fitlimit
&& maxfit
>= 0)
3948 else if (! fitlimit
)
3965 * Output an encoded parameter string.
3969 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3970 size_t valueoff
, int index
)
3972 size_t outlen
= 0, n
;
3973 char *endptr
= output
+ len
, *p
;
3976 * First, output the marker for an encoded string.
3984 * If the index is 0, output the character set and language tag.
3985 * If theses were NULL, they should have already been filled in
3990 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3994 if (output
> endptr
) {
3995 inform("Internal error: parameter buffer overflow");
4001 * Copy over the value, encoding if necessary
4004 p
= pm
->pm_value
+ valueoff
;
4005 while (valuelen
-- > 0) {
4006 if (isparamencode(*p
)) {
4007 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4014 if (output
> endptr
) {
4015 inform("Internal error: parameter buffer overflow");
4026 * Output a "normal" parameter, without encoding. Be sure to escape
4027 * quotes and backslashes if necessary.
4031 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4035 char *endptr
= output
+ len
, *p
;
4041 p
= pm
->pm_value
+ valueoff
;
4043 while (valuelen
-- > 0) {
4054 if (output
> endptr
) {
4055 inform("Internal error: parameter buffer overflow");
4060 if (output
- 2 > endptr
) {
4061 inform("Internal error: parameter buffer overflow");
4072 * Add a parameter to the parameter linked list
4076 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4081 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4082 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4085 (*last
)->pm_next
= pm
;
4096 * Either replace a current parameter with a new value, or add the parameter
4097 * to the parameter linked list.
4101 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4105 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4106 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4108 * If nocopy is set, it's assumed that we own both name
4109 * and value. We don't need name, so we discard it now.
4114 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4119 return add_param(first
, last
, name
, value
, nocopy
);
4123 * Retrieve a parameter value from a parameter linked list. If the parameter
4124 * value needs converted to the local character set, do that now.
4128 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4130 while (first
!= NULL
) {
4131 if (strcasecmp(name
, first
->pm_name
) == 0) {
4133 return first
->pm_value
;
4134 return getcpy(get_param_value(first
, replace
));
4136 first
= first
->pm_next
;
4143 * Return a parameter value, converting to the local character set if
4148 get_param_value(PM pm
, char replace
)
4150 static char buffer
[4096]; /* I hope no parameters are larger */
4151 size_t bufsize
= sizeof(buffer
);
4156 ICONV_CONST
char *p
;
4157 #else /* HAVE_ICONV */
4159 #endif /* HAVE_ICONV */
4164 * If we don't have a character set indicated, it's assumed to be
4165 * US-ASCII. If it matches our character set, we don't need to convert
4169 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4170 strlen(pm
->pm_charset
))) {
4171 return pm
->pm_value
;
4175 * In this case, we need to convert. If we have iconv support, use
4176 * that. Otherwise, go through and simply replace every non-ASCII
4177 * character with the substitution character.
4182 bufsize
= sizeof(buffer
);
4183 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4185 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4186 if (cd
== (iconv_t
) -1) {
4190 inbytes
= strlen(pm
->pm_value
);
4194 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4195 if (errno
!= EILSEQ
) {
4200 * Reset shift state, substitute our character,
4201 * try to restart conversion.
4204 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4217 for (++p
, --inbytes
;
4218 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4237 #endif /* HAVE_ICONV */
4240 * Take everything non-ASCII and substitute the replacement character
4244 bufsize
= sizeof(buffer
);
4245 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4246 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))