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/r1bindex.h"
10 #include "sbr/ruserpass.h"
11 #include "sbr/fmt_rfc2047.h"
13 #include "sbr/check_charset.h"
14 #include "sbr/getcpy.h"
15 #include "sbr/context_find.h"
16 #include "sbr/pidstatus.h"
17 #include "sbr/arglist.h"
18 #include "sbr/error.h"
24 #include "h/mhparse.h"
27 #include "h/mhcachesbr.h"
28 #include "sbr/m_mktemp.h"
32 #endif /* HAVE_ICONV */
33 #include "sbr/base64.h"
38 int checksw
= 0; /* check Content-MD5 field */
41 * These are for mhfixmsg to:
42 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
44 * 2) Suppress the warning about bogus multipart content, and report it.
45 * 3) Suppress the warning about extraneous trailing ';' in header parameter
48 bool skip_mp_cte_check
;
49 bool suppress_bogus_mp_content_warning
;
50 bool bogus_mp_content
;
51 bool suppress_extraneous_trailing_semicolon_warning
;
54 * By default, suppress warning about multiple MIME-Version header fields.
56 bool suppress_multiple_mime_version_warning
= true;
58 /* list of preferred type/subtype pairs, for -prefer */
59 mime_type_subtype mime_preference
[NPREFS
];
64 * Structures for TEXT messages
66 struct k2v SubText
[] = {
67 { "plain", TEXT_PLAIN
},
68 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
69 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
70 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
73 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
76 * Structures for MULTIPART messages
78 struct k2v SubMultiPart
[] = {
79 { "mixed", MULTI_MIXED
},
80 { "alternative", MULTI_ALTERNATE
},
81 { "digest", MULTI_DIGEST
},
82 { "parallel", MULTI_PARALLEL
},
83 { "related", MULTI_RELATED
},
84 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
88 * Structures for MESSAGE messages
90 struct k2v SubMessage
[] = {
91 { "rfc822", MESSAGE_RFC822
},
92 { "partial", MESSAGE_PARTIAL
},
93 { "external-body", MESSAGE_EXTERNAL
},
94 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
98 * Structure for APPLICATION messages
100 struct k2v SubApplication
[] = {
101 { "octet-stream", APPLICATION_OCTETS
},
102 { "postscript", APPLICATION_POSTSCRIPT
},
103 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
107 * Mapping of names of CTE types in mhbuild directives
109 static struct k2v EncodingType
[] = {
112 { "q-p", CE_QUOTED
},
113 { "quoted-printable", CE_QUOTED
},
114 { "b64", CE_BASE64
},
115 { "base64", CE_BASE64
},
123 static CT
get_content (FILE *, char *, int);
124 static int get_comment (const char *, const char *, char **, char **);
126 static int InitGeneric (CT
);
127 static int InitText (CT
);
128 static int InitMultiPart (CT
);
129 static void reverse_parts (CT
);
130 static void prefer_parts(CT ct
);
131 static int InitMessage (CT
);
132 static int InitApplication (CT
);
133 static int init_encoding (CT
, OpenCEFunc
);
134 static unsigned long size_encoding (CT
);
135 static int InitBase64 (CT
);
136 static int openBase64 (CT
, char **);
137 static int InitQuoted (CT
);
138 static int openQuoted (CT
, char **);
139 static int Init7Bit (CT
);
140 static int openExternal (CT
, CT
, CE
, char **, int *);
141 static int InitFile (CT
);
142 static int openFile (CT
, char **);
143 static int InitFTP (CT
);
144 static int openFTP (CT
, char **);
145 static int InitMail (CT
);
146 static int openMail (CT
, char **);
147 static int readDigest (CT
, char *);
148 static int get_leftover_mp_content (CT
, int);
149 static int InitURL (CT
);
150 static int openURL (CT
, char **);
151 static int parse_header_attrs (const char *, const char *, char **, PM
*,
153 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
154 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
155 static int get_dispo (char *, CT
, int);
157 struct str2init str2cts
[] = {
158 { "application", CT_APPLICATION
, InitApplication
},
159 { "audio", CT_AUDIO
, InitGeneric
},
160 { "image", CT_IMAGE
, InitGeneric
},
161 { "message", CT_MESSAGE
, InitMessage
},
162 { "multipart", CT_MULTIPART
, InitMultiPart
},
163 { "text", CT_TEXT
, InitText
},
164 { "video", CT_VIDEO
, InitGeneric
},
165 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
166 { NULL
, CT_UNKNOWN
, NULL
},
169 struct str2init str2ces
[] = {
170 { "base64", CE_BASE64
, InitBase64
},
171 { "quoted-printable", CE_QUOTED
, InitQuoted
},
172 { "8bit", CE_8BIT
, Init7Bit
},
173 { "7bit", CE_7BIT
, Init7Bit
},
174 { "binary", CE_BINARY
, Init7Bit
},
175 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
176 { NULL
, CE_UNKNOWN
, NULL
},
180 * NOTE WELL: si_key MUST NOT have value of NOTOK
182 * si_val is 1 if access method is anonymous.
184 struct str2init str2methods
[] = {
185 { "afs", 1, InitFile
},
186 { "anon-ftp", 1, InitFTP
},
187 { "ftp", 0, InitFTP
},
188 { "local-file", 0, InitFile
},
189 { "mail-server", 0, InitMail
},
190 { "url", 0, InitURL
},
196 * Main entry point for parsing a MIME message or file.
197 * It returns the Content structure for the top level
198 * entity in the file.
202 parse_mime (char *file
)
211 bogus_mp_content
= false;
214 * Check if file is actually standard input
216 if ((is_stdin
= !(strcmp (file
, "-")))) {
217 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
219 advise("mhparse", "unable to create temporary file in %s",
223 file
= mh_xstrdup(tfile
);
225 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
226 if (fwrite(buffer
, 1, n
, fp
) != n
) {
227 (void) m_unlink (file
);
228 advise (file
, "error copying to temporary file");
234 if (ferror (stdin
)) {
235 (void) m_unlink (file
);
236 advise ("stdin", "error reading");
240 (void) m_unlink (file
);
241 advise (file
, "error writing");
244 fseek (fp
, 0L, SEEK_SET
);
245 } else if (stat (file
, &statbuf
) == NOTOK
) {
246 advise (file
, "unable to stat");
248 } else if (S_ISDIR(statbuf
.st_mode
)) {
249 /* Don't try to parse a directory. */
250 inform("%s is a directory", file
);
252 } else if ((fp
= fopen (file
, "r")) == NULL
) {
253 advise (file
, "unable to read");
257 if (!(ct
= get_content (fp
, file
, 1))) {
259 (void) m_unlink (file
);
260 inform("unable to decode %s", file
);
265 ct
->c_unlink
= 1; /* temp file to remove */
269 if (ct
->c_end
== 0L) {
270 fseek (fp
, 0L, SEEK_END
);
271 ct
->c_end
= ftell (fp
);
274 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
286 * Main routine for reading/parsing the headers
287 * of a message content.
289 * toplevel = 1 # we are at the top level of the message
290 * toplevel = 0 # we are inside message type or multipart type
291 * # other than multipart/digest
292 * toplevel = -1 # we are inside multipart/digest
293 * NB: on failure we will fclose(in)!
297 get_content (FILE *in
, char *file
, int toplevel
)
300 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
304 m_getfld_state_t gstate
;
306 /* allocate the content structure */
309 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
310 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
313 * Parse the header fields for this
314 * content into a linked list.
316 gstate
= m_getfld_state_init(in
);
317 m_getfld_track_filepos2(&gstate
);
318 for (compnum
= 1;;) {
319 int bufsz
= sizeof buf
;
320 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
325 /* get copies of the buffers */
326 np
= mh_xstrdup(name
);
327 vp
= mh_xstrdup(buf
);
329 /* if necessary, get rest of field */
330 while (state
== FLDPLUS
) {
332 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
333 vp
= add (buf
, vp
); /* add to previous value */
336 /* Now add the header data to the list */
337 add_header (ct
, np
, vp
);
339 /* continue, to see if this isn't the last header field */
340 ct
->c_begin
= ftell (in
) + 1;
344 /* There are two cases. The unusual one is when there is no
345 * blank line between the headers and the body. This is
346 * indicated by the name of the header starting with `:'.
348 * For both cases, normal first, `1' is the desired c_begin
349 * file position for the start of the body, and `2' is the
350 * file position when buf is returned.
352 * f o o : b a r \n \n b o d y \n bufsz = 6
354 * f o o : b a r \n b o d y \n bufsz = 4
357 * For the normal case, bufsz includes the
358 * header-terminating `\n', even though it is not in buf,
359 * but bufsz isn't affected when it's missing in the unusual
361 if (name
[0] == ':') {
362 ct
->c_begin
= ftell(in
) - bufsz
;
364 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
369 ct
->c_begin
= ftell (in
);
374 die("message format error in component #%d", compnum
);
377 die("getfld() returned %d", state
);
380 /* break out of the loop */
383 m_getfld_state_destroy (&gstate
);
386 * Read the content headers. We will parse the
387 * MIME related header fields into their various
388 * structures and set internal flags related to
389 * content type/subtype, etc.
392 hp
= ct
->c_first_hf
; /* start at first header field */
394 /* Get MIME-Version field */
395 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
400 vrsn
= mh_xstrdup(FENDNULL(hp
->value
));
402 /* Now, cleanup this field */
405 while (isspace ((unsigned char) *cp
))
407 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
409 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
410 if (!isspace ((unsigned char) *dp
))
414 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
417 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
420 for (dp
= cp
; istoken (*dp
); dp
++)
424 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
427 inform("message %s has unknown value for %s: field (%s), continuing...",
428 ct
->c_file
, VRSN_FIELD
, cp
);
433 if (! suppress_multiple_mime_version_warning
)
434 inform("message %s has multiple %s: fields",
435 ct
->c_file
, VRSN_FIELD
);
439 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
440 /* Get Content-Type field */
441 struct str2init
*s2i
;
442 CI ci
= &ct
->c_ctinfo
;
444 /* Check if we've already seen a Content-Type header */
446 inform("message %s has multiple %s: fields",
447 ct
->c_file
, TYPE_FIELD
);
451 /* Parse the Content-Type field */
452 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
456 * Set the Init function and the internal
457 * flag for this content type.
459 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
460 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
462 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
464 ct
->c_type
= s2i
->si_val
;
465 ct
->c_ctinitfnx
= s2i
->si_init
;
467 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
468 /* Get Content-Transfer-Encoding field */
470 struct str2init
*s2i
;
473 * Check if we've already seen the
474 * Content-Transfer-Encoding field
477 inform("message %s has multiple %s: fields",
478 ct
->c_file
, ENCODING_FIELD
);
482 /* get copy of this field */
483 ct
->c_celine
= cp
= mh_xstrdup(FENDNULL(hp
->value
));
485 while (isspace ((unsigned char) *cp
))
487 for (dp
= cp
; istoken (*dp
); dp
++)
493 * Find the internal flag and Init function
494 * for this transfer encoding.
496 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
497 if (!strcasecmp (cp
, s2i
->si_key
))
499 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
502 ct
->c_encoding
= s2i
->si_val
;
504 /* Call the Init function for this encoding */
505 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
508 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
509 /* Get Content-MD5 field */
515 if (ct
->c_digested
) {
516 inform("message %s has multiple %s: fields",
517 ct
->c_file
, MD5_FIELD
);
521 ep
= cp
= mh_xstrdup(FENDNULL(hp
->value
)); /* get a copy */
523 while (isspace ((unsigned char) *cp
))
525 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
527 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
528 if (!isspace ((unsigned char) *dp
))
532 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
535 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
540 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
548 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
549 /* Get Content-ID field */
550 ct
->c_id
= add (hp
->value
, ct
->c_id
);
552 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
553 /* Get Content-Description field */
554 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
556 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
557 /* Get Content-Disposition field */
558 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
563 hp
= hp
->next
; /* next header field */
567 * Check if we saw a Content-Type field.
568 * If not, then assign a default value for
569 * it, and the Init function.
573 * If we are inside a multipart/digest message,
574 * so default type is message/rfc822
577 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
579 ct
->c_type
= CT_MESSAGE
;
580 ct
->c_ctinitfnx
= InitMessage
;
583 * Else default type is text/plain
585 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
587 ct
->c_type
= CT_TEXT
;
588 ct
->c_ctinitfnx
= InitText
;
592 /* Use default Transfer-Encoding, if necessary */
594 ct
->c_encoding
= CE_7BIT
;
607 * small routine to add header field to list
611 add_header (CT ct
, char *name
, char *value
)
615 /* allocate header field structure */
618 /* link data into header structure */
623 /* link header structure into the list */
624 if (ct
->c_first_hf
== NULL
) {
625 ct
->c_first_hf
= hp
; /* this is the first */
628 ct
->c_last_hf
->next
= hp
; /* add it to the end */
637 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
638 * directives. Fills in the information of the CTinfo structure.
641 get_ctinfo (char *cp
, CT ct
, int magic
)
650 /* store copy of Content-Type line */
651 cp
= ct
->c_ctline
= mh_xstrdup(FENDNULL(cp
));
653 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
656 /* change newlines to spaces */
657 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
660 /* trim trailing spaces */
661 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
662 if (!isspace ((unsigned char) *dp
))
667 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
669 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
670 &ci
->ci_comment
) == NOTOK
)
673 for (dp
= cp
; istoken (*dp
); dp
++)
677 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
682 inform("invalid %s: field in message %s (empty type)",
683 TYPE_FIELD
, ct
->c_file
);
686 to_lower(ci
->ci_type
);
688 while (isspace ((unsigned char) *cp
))
691 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
692 &ci
->ci_comment
) == NOTOK
)
697 ci
->ci_subtype
= mh_xstrdup("");
702 while (isspace ((unsigned char) *cp
))
705 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
706 &ci
->ci_comment
) == NOTOK
)
709 for (dp
= cp
; istoken (*dp
); dp
++)
713 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
717 if (!*ci
->ci_subtype
) {
718 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
719 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
722 to_lower(ci
->ci_subtype
);
725 while (isspace ((unsigned char) *cp
))
728 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
729 &ci
->ci_comment
) == NOTOK
)
732 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
733 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
734 &ci
->ci_comment
)) != OK
) {
735 return status
== NOTOK
? NOTOK
: OK
;
739 * Get any <Content-Id> given in buffer
741 if (magic
&& *cp
== '<') {
744 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
745 inform("invalid ID in message %s", ct
->c_file
);
751 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
757 while (isspace ((unsigned char) *cp
))
762 * Get any [Content-Description] given in buffer.
764 if (magic
&& *cp
== '[') {
766 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
770 inform("invalid description in message %s", ct
->c_file
);
778 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
784 while (isspace ((unsigned char) *cp
))
789 * Get any {Content-Disposition} given in buffer.
791 if (magic
&& *cp
== '{') {
793 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
797 inform("invalid disposition in message %s", ct
->c_file
);
805 if (get_dispo(cp
, ct
, 1) != OK
)
811 while (isspace ((unsigned char) *cp
))
816 * Get any extension directives (right now just the content transfer
817 * encoding, but maybe others) that we care about.
820 if (magic
&& *cp
== '*') {
822 * See if it's a CTE we match on
827 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
831 inform("invalid null transfer encoding specification");
838 ct
->c_reqencoding
= CE_UNKNOWN
;
840 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
841 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
842 ct
->c_reqencoding
= kv
->kv_value
;
847 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
848 inform("invalid CTE specification: \"%s\"", dp
);
852 while (isspace ((unsigned char) *cp
))
857 * Check if anything is left over
861 ci
->ci_magic
= mh_xstrdup(cp
);
863 /* If there is a Content-Disposition header and it doesn't
864 have a *filename=, extract it from the magic contents.
865 The r1bindex call skips any leading directory
867 if (ct
->c_dispo_type
&&
868 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
869 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
870 r1bindex(ci
->ci_magic
, '/'), 0);
874 inform("extraneous information in message %s's %s: field\n"
875 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
883 * Parse out a Content-Disposition header. A lot of this is cribbed from
887 get_dispo (char *cp
, CT ct
, int buildflag
)
889 char *dp
, *dispoheader
;
894 * Save the whole copy of the Content-Disposition header, unless we're
895 * processing a mhbuild directive. A NULL c_dispo will be a flag to
896 * mhbuild that the disposition header needs to be generated at that
900 dispoheader
= cp
= mh_xstrdup(FENDNULL(cp
));
902 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
905 /* change newlines to spaces */
906 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
909 /* trim trailing spaces */
910 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
911 if (!isspace ((unsigned char) *dp
))
916 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
918 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
924 for (dp
= cp
; istoken (*dp
); dp
++)
928 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
932 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
935 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
936 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
938 if (status
== NOTOK
) {
943 inform("extraneous information in message %s's %s: field\n (%s)",
944 ct
->c_file
, DISPO_FIELD
, cp
);
950 ct
->c_dispo
= dispoheader
;
957 get_comment (const char *filename
, const char *fieldname
, char **ap
,
962 char c
, buffer
[BUFSIZ
], *dp
;
972 inform("invalid comment in message %s's %s: field",
973 filename
, fieldname
);
978 if ((c
= *cp
++) == '\0')
1001 if ((dp
= *commentp
)) {
1002 *commentp
= concat (dp
, " ", buffer
, NULL
);
1005 *commentp
= mh_xstrdup(buffer
);
1009 while (isspace ((unsigned char) *cp
))
1020 * Handles content types audio, image, and video.
1021 * There's not much to do right here.
1029 return OK
; /* not much to do here */
1040 char buffer
[BUFSIZ
];
1045 CI ci
= &ct
->c_ctinfo
;
1047 /* check for missing subtype */
1048 if (!*ci
->ci_subtype
)
1049 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1052 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1054 /* allocate text character set structure */
1056 ct
->c_ctparams
= (void *) t
;
1058 /* scan for charset parameter */
1059 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1060 if (!strcasecmp (pm
->pm_name
, "charset"))
1063 /* check if content specified a character set */
1065 chset
= pm
->pm_value
;
1066 t
->tx_charset
= CHARSET_SPECIFIED
;
1068 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1072 * If we can not handle character set natively,
1073 * then check profile for string to modify the
1074 * terminal or display method.
1076 * termproc is for mhshow, though mhlist -debug prints it, too.
1078 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1079 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1080 if ((cp
= context_find (buffer
)))
1081 ct
->c_termproc
= mh_xstrdup(cp
);
1093 InitMultiPart (CT ct
)
1103 struct multipart
*m
;
1104 struct part
*part
, **next
;
1105 CI ci
= &ct
->c_ctinfo
;
1110 * The encoding for multipart messages must be either
1111 * 7bit, 8bit, or binary (per RFC 2045).
1113 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1114 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1115 /* Copy the Content-Transfer-Encoding header field body so we can
1116 remove any trailing whitespace and leading blanks from it. */
1117 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1119 bp
= cte
+ strlen (cte
) - 1;
1120 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1121 for (bp
= cte
; isblank((unsigned char)*bp
); ++bp
) continue;
1123 inform("\"%s/%s\" type in message %s must be encoded in\n"
1124 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1125 "mhfixmsg -fixcte can fix it, or\n"
1126 "manually edit the file and change the \"%s\"\n"
1127 "Content-Transfer-Encoding to one of those. For now, continuing...",
1128 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1135 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1138 * Check for "boundary" parameter, which is
1139 * required for multipart messages.
1142 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1143 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1149 /* complain if boundary parameter is missing */
1151 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1152 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1156 /* allocate primary structure for multipart info */
1158 ct
->c_ctparams
= (void *) m
;
1160 /* check if boundary parameter contains only whitespace characters */
1161 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1164 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1165 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1169 /* remove trailing whitespace from boundary parameter */
1170 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1171 if (!isspace ((unsigned char) *dp
))
1175 /* record boundary separators */
1176 m
->mp_start
= concat (bp
, "\n", NULL
);
1177 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1179 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1180 advise (ct
->c_file
, "unable to open for reading");
1184 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1186 next
= &m
->mp_parts
;
1190 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1195 if (bufp
[0] != '-' || bufp
[1] != '-')
1198 if (strcmp (bufp
+ 2, m
->mp_start
))
1203 next
= &part
->mp_next
;
1205 if (!(p
= get_content (fp
, ct
->c_file
,
1206 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1214 fseek (fp
, pos
, SEEK_SET
);
1217 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1221 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1222 if (p
->c_end
< p
->c_begin
)
1223 p
->c_begin
= p
->c_end
;
1228 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1233 if (! suppress_bogus_mp_content_warning
) {
1234 inform("bogus multipart content in message %s", ct
->c_file
);
1236 bogus_mp_content
= true;
1238 if (!inout
&& part
) {
1240 p
->c_end
= ct
->c_end
;
1242 if (p
->c_begin
>= p
->c_end
) {
1243 for (next
= &m
->mp_parts
; *next
!= part
;
1244 next
= &((*next
)->mp_next
))
1253 /* reverse the order of the parts for multipart/alternative */
1254 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1260 * label all subparts with part number, and
1261 * then initialize the content of the subpart.
1266 char partnam
[BUFSIZ
];
1269 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1270 pp
= partnam
+ strlen (partnam
);
1275 for (part
= m
->mp_parts
, partnum
= 1; part
;
1276 part
= part
->mp_next
, partnum
++) {
1279 sprintf (pp
, "%d", partnum
);
1280 p
->c_partno
= mh_xstrdup(partnam
);
1282 /* initialize the content of the subparts */
1283 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1292 get_leftover_mp_content (ct
, 1);
1293 get_leftover_mp_content (ct
, 0);
1303 * reverse the order of the parts of a multipart/alternative,
1304 * presumably to put the "most favored" alternative first, for
1305 * ease of choosing/displaying it later on. from a mail message on
1306 * nmh-workers, from kenh:
1307 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1308 * see code in mhn that did the same thing... According to the RCS
1309 * logs, that code was around from the initial checkin of mhn.c by
1310 * John Romine in 1992, which is as far back as we have."
1313 reverse_parts (CT ct
)
1315 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1319 /* Reverse the order of its parts by walking the mp_parts list
1320 and pushing each node to the front. */
1321 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1322 next
= part
->mp_next
;
1323 part
->mp_next
= m
->mp_parts
;
1329 move_preferred_part(CT ct
, mime_type_subtype
*pref
)
1331 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1332 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1336 /* move the matching part(s) to the head of the list: walk the
1337 * list of parts, move matching parts to a new list (maintaining
1338 * their order), and finally, concatenate the old list onto the
1345 head
->mp_next
= m
->mp_parts
;
1346 nhead
->mp_next
= NULL
;
1350 part
= head
->mp_next
;
1351 while (part
!= NULL
) {
1352 ci
= &part
->mp_part
->c_ctinfo
;
1353 if (!strcasecmp(ci
->ci_type
, pref
->type
) &&
1355 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1356 prev
->mp_next
= part
->mp_next
;
1357 part
->mp_next
= NULL
;
1358 ntail
->mp_next
= part
;
1360 part
= prev
->mp_next
;
1363 part
= prev
->mp_next
;
1366 ntail
->mp_next
= head
->mp_next
;
1367 m
->mp_parts
= nhead
->mp_next
;
1371 * move parts that match the user's preferences (-prefer) to the head
1372 * of the line. process preferences in reverse so first one given
1373 * ends up first in line
1379 for (i
= 0; i
< npreferred
; i
++)
1380 move_preferred_part(ct
, mime_preference
+ i
);
1385 /* parse_mime() arranges alternates in reverse (priority) order. This
1386 function can be used to reverse them back. This will put, for
1387 example, a text/plain part before a text/html part in a
1388 multipart/alternative part, for example, where it belongs. */
1390 reverse_alternative_parts (CT ct
)
1392 if (ct
->c_type
== CT_MULTIPART
) {
1393 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1396 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1400 /* And call recursively on each part of a multipart. */
1401 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1402 reverse_alternative_parts (part
->mp_part
);
1415 CI ci
= &ct
->c_ctinfo
;
1417 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1418 inform("\"%s/%s\" type in message %s should be encoded in "
1419 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1424 /* check for missing subtype */
1425 if (!*ci
->ci_subtype
)
1426 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1429 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1431 switch (ct
->c_subtype
) {
1432 case MESSAGE_RFC822
:
1435 case MESSAGE_PARTIAL
:
1441 ct
->c_ctparams
= (void *) p
;
1443 /* scan for parameters "id", "number", and "total" */
1444 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1445 if (!strcasecmp (pm
->pm_name
, "id")) {
1446 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1449 if (!strcasecmp (pm
->pm_name
, "number")) {
1450 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1451 || p
->pm_partno
< 1) {
1453 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1454 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1455 ct
->c_file
, TYPE_FIELD
);
1460 if (!strcasecmp (pm
->pm_name
, "total")) {
1461 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1470 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1471 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1472 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1478 case MESSAGE_EXTERNAL
:
1486 ct
->c_ctparams
= (void *) e
;
1489 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1490 advise (ct
->c_file
, "unable to open for reading");
1494 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1496 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1504 p
->c_ceopenfnx
= NULL
;
1505 if ((exresult
= params_external (ct
, 0)) != NOTOK
1506 && p
->c_ceopenfnx
== openMail
) {
1510 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1512 content_error (NULL
, ct
,
1513 "empty body for access-type=mail-server");
1517 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1518 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1520 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1522 adios ("failed", "fread");
1525 die("unexpected EOF from fread");
1528 bp
+= cc
, size
-= cc
;
1535 p
->c_end
= p
->c_begin
;
1540 if (exresult
== NOTOK
)
1542 if (e
->eb_flags
== NOTOK
)
1545 switch (p
->c_type
) {
1550 if (p
->c_subtype
!= MESSAGE_RFC822
)
1554 e
->eb_partno
= ct
->c_partno
;
1556 (*p
->c_ctinitfnx
) (p
);
1571 params_external (CT ct
, int composing
)
1574 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1575 CI ci
= &ct
->c_ctinfo
;
1577 ct
->c_ceopenfnx
= NULL
;
1578 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1579 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1580 struct str2init
*s2i
;
1581 CT p
= e
->eb_content
;
1583 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1584 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1587 e
->eb_access
= pm
->pm_value
;
1588 e
->eb_flags
= NOTOK
;
1589 p
->c_encoding
= CE_EXTERNAL
;
1592 e
->eb_access
= s2i
->si_key
;
1593 e
->eb_flags
= s2i
->si_val
;
1594 p
->c_encoding
= CE_EXTERNAL
;
1596 /* Call the Init function for this external type */
1597 if ((*s2i
->si_init
)(p
) == NOTOK
)
1601 if (!strcasecmp (pm
->pm_name
, "name")) {
1602 e
->eb_name
= pm
->pm_value
;
1605 if (!strcasecmp (pm
->pm_name
, "permission")) {
1606 e
->eb_permission
= pm
->pm_value
;
1609 if (!strcasecmp (pm
->pm_name
, "site")) {
1610 e
->eb_site
= pm
->pm_value
;
1613 if (!strcasecmp (pm
->pm_name
, "directory")) {
1614 e
->eb_dir
= pm
->pm_value
;
1617 if (!strcasecmp (pm
->pm_name
, "mode")) {
1618 e
->eb_mode
= pm
->pm_value
;
1621 if (!strcasecmp (pm
->pm_name
, "size")) {
1622 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1625 if (!strcasecmp (pm
->pm_name
, "server")) {
1626 e
->eb_server
= pm
->pm_value
;
1629 if (!strcasecmp (pm
->pm_name
, "subject")) {
1630 e
->eb_subject
= pm
->pm_value
;
1633 if (!strcasecmp (pm
->pm_name
, "url")) {
1635 * According to RFC 2017, we have to remove all whitespace from
1639 char *u
, *p
= pm
->pm_value
;
1640 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1642 for (; *p
!= '\0'; p
++) {
1643 if (! isspace((unsigned char) *p
))
1650 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1651 e
->eb_body
= getcpy (pm
->pm_value
);
1656 if (!e
->eb_access
) {
1657 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1658 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1671 InitApplication (CT ct
)
1673 CI ci
= &ct
->c_ctinfo
;
1676 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1683 * TRANSFER ENCODINGS
1687 init_encoding (CT ct
, OpenCEFunc openfnx
)
1689 ct
->c_ceopenfnx
= openfnx
;
1690 ct
->c_ceclosefnx
= close_encoding
;
1691 ct
->c_cesizefnx
= size_encoding
;
1698 close_encoding (CT ct
)
1700 CE ce
= &ct
->c_cefile
;
1709 static unsigned long
1710 size_encoding (CT ct
)
1715 CE ce
= &ct
->c_cefile
;
1718 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1719 return (long) st
.st_size
;
1722 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1723 return (long) st
.st_size
;
1727 if (ct
->c_encoding
== CE_EXTERNAL
)
1728 return ct
->c_end
- ct
->c_begin
;
1731 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1732 return ct
->c_end
- ct
->c_begin
;
1734 if (fstat (fd
, &st
) != NOTOK
)
1735 size
= (long) st
.st_size
;
1739 (*ct
->c_ceclosefnx
) (ct
);
1751 return init_encoding (ct
, openBase64
);
1756 openBase64 (CT ct
, char **file
)
1760 bool own_ct_fp
= false;
1761 char *cp
, *buffer
= NULL
;
1762 /* sbeck -- handle suffixes */
1764 CE ce
= &ct
->c_cefile
;
1765 unsigned char *decoded
;
1767 unsigned char digest
[16];
1770 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1775 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1776 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1782 if (*file
== NULL
) {
1785 ce
->ce_file
= mh_xstrdup(*file
);
1789 /* sbeck@cise.ufl.edu -- handle suffixes */
1791 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1792 if (ce
->ce_unlink
) {
1793 /* Create temporary file with filename extension. */
1794 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1795 die("unable to create temporary file in %s",
1799 ce
->ce_file
= add (cp
, ce
->ce_file
);
1801 } else if (*file
== NULL
) {
1803 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1804 die("unable to create temporary file in %s",
1807 ce
->ce_file
= mh_xstrdup(tempfile
);
1810 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1811 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1815 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1816 die("internal error(1)");
1818 buffer
= mh_xmalloc (len
+ 1);
1821 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1822 content_error (ct
->c_file
, ct
, "unable to open for reading");
1828 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1831 switch (cc
= read (fd
, cp
, len
)) {
1833 content_error (ct
->c_file
, ct
, "error reading from");
1837 content_error (NULL
, ct
, "premature eof");
1848 /* decodeBase64() requires null-terminated input. */
1851 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1852 ct
->c_digested
? digest
: NULL
) != OK
)
1857 unsigned char *decoded_p
= decoded
;
1858 for (i
= 0; i
< decoded_len
; ++i
) {
1859 putc (*decoded_p
++, ce
->ce_fp
);
1862 if (ferror (ce
->ce_fp
)) {
1863 content_error (ce
->ce_file
, ct
, "error writing to");
1867 if (ct
->c_digested
) {
1868 if (memcmp(digest
, ct
->c_digest
,
1870 content_error (NULL
, ct
,
1871 "content integrity suspect (digest mismatch) -- continuing");
1874 fprintf (stderr
, "content integrity confirmed\n");
1880 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1882 if (fflush (ce
->ce_fp
)) {
1883 content_error (ce
->ce_file
, ct
, "error writing to");
1887 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1890 *file
= ce
->ce_file
;
1896 return fileno (ce
->ce_fp
);
1903 free_encoding (ct
, 0);
1913 static char hex2nib
[0x80] = {
1914 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1915 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1916 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1917 0x00, 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, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1921 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1922 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1923 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1924 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1925 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1926 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1927 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1928 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1929 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1936 return init_encoding (ct
, openQuoted
);
1941 openQuoted (CT ct
, char **file
)
1943 int cc
, digested
, len
, quoted
;
1944 bool own_ct_fp
= false;
1950 CE ce
= &ct
->c_cefile
;
1951 /* sbeck -- handle suffixes */
1956 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1961 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1962 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1968 if (*file
== NULL
) {
1971 ce
->ce_file
= mh_xstrdup(*file
);
1975 /* sbeck@cise.ufl.edu -- handle suffixes */
1977 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1978 if (ce
->ce_unlink
) {
1979 /* Create temporary file with filename extension. */
1980 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1981 die("unable to create temporary file in %s",
1985 ce
->ce_file
= add (cp
, ce
->ce_file
);
1987 } else if (*file
== NULL
) {
1989 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1990 die("unable to create temporary file in %s",
1993 ce
->ce_file
= mh_xstrdup(tempfile
);
1996 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1997 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2001 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2002 die("internal error(2)");
2005 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2006 content_error (ct
->c_file
, ct
, "unable to open for reading");
2012 if ((digested
= ct
->c_digested
))
2013 MD5Init (&mdContext
);
2017 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2019 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2020 content_error (NULL
, ct
, "premature eof");
2024 if ((cc
= gotlen
) > len
)
2028 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2029 if (!isspace ((unsigned char) *ep
))
2034 for (; cp
< ep
; cp
++) {
2036 /* in an escape sequence */
2038 /* at byte 1 of an escape sequence */
2039 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2040 /* next is byte 2 */
2043 /* at byte 2 of an escape sequence */
2045 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2046 putc (mask
, ce
->ce_fp
);
2048 MD5Update (&mdContext
, &mask
, 1);
2049 if (ferror (ce
->ce_fp
)) {
2050 content_error (ce
->ce_file
, ct
, "error writing to");
2053 /* finished escape sequence; next may be literal or a new
2054 * escape sequence */
2057 /* on to next byte */
2061 /* not in an escape sequence */
2063 /* starting an escape sequence, or invalid '='? */
2064 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2065 /* "=\n" soft line break, eat the \n */
2069 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2070 /* We don't have 2 bytes left, so this is an invalid
2071 * escape sequence; just show the raw bytes (below). */
2072 } else if (isxdigit ((unsigned char) cp
[1]) &&
2073 isxdigit ((unsigned char) cp
[2])) {
2074 /* Next 2 bytes are hex digits, making this a valid escape
2075 * sequence; let's decode it (above). */
2079 /* One or both of the next 2 is out of range, making this
2080 * an invalid escape sequence; just show the raw bytes
2084 /* Just show the raw byte. */
2085 putc (*cp
, ce
->ce_fp
);
2088 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2090 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2093 if (ferror (ce
->ce_fp
)) {
2094 content_error (ce
->ce_file
, ct
, "error writing to");
2100 content_error (NULL
, ct
,
2101 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2105 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2107 if (fflush (ce
->ce_fp
)) {
2108 content_error (ce
->ce_file
, ct
, "error writing to");
2113 unsigned char digest
[16];
2115 MD5Final (digest
, &mdContext
);
2116 if (memcmp(digest
, ct
->c_digest
,
2118 content_error (NULL
, ct
,
2119 "content integrity suspect (digest mismatch) -- continuing");
2121 fprintf (stderr
, "content integrity confirmed\n");
2124 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2127 *file
= ce
->ce_file
;
2133 return fileno (ce
->ce_fp
);
2136 free_encoding (ct
, 0);
2153 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2156 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2162 open7Bit (CT ct
, char **file
)
2165 bool own_ct_fp
= false;
2166 char buffer
[BUFSIZ
];
2167 /* sbeck -- handle suffixes */
2170 CE ce
= &ct
->c_cefile
;
2173 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2178 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2179 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2185 if (*file
== NULL
) {
2188 ce
->ce_file
= mh_xstrdup(*file
);
2192 /* sbeck@cise.ufl.edu -- handle suffixes */
2194 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2195 if (ce
->ce_unlink
) {
2196 /* Create temporary file with filename extension. */
2197 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2198 die("unable to create temporary file in %s",
2202 ce
->ce_file
= add (cp
, ce
->ce_file
);
2204 } else if (*file
== NULL
) {
2206 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2207 die("unable to create temporary file in %s",
2210 ce
->ce_file
= mh_xstrdup(tempfile
);
2213 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2214 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2218 if (ct
->c_type
== CT_MULTIPART
) {
2219 CI ci
= &ct
->c_ctinfo
;
2223 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2224 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2225 + 1 + strlen (ci
->ci_subtype
);
2226 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2229 fputs (buffer
, ce
->ce_fp
);
2233 if (ci
->ci_comment
) {
2234 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2235 fputs ("\n\t", ce
->ce_fp
);
2239 putc (' ', ce
->ce_fp
);
2242 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2245 fprintf (ce
->ce_fp
, "\n");
2247 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2249 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2251 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2252 fprintf (ce
->ce_fp
, "\n");
2255 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2256 die("internal error(3)");
2259 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2260 content_error (ct
->c_file
, ct
, "unable to open for reading");
2266 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2268 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2270 content_error (ct
->c_file
, ct
, "error reading from");
2274 content_error (NULL
, ct
, "premature eof");
2282 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2283 advise ("open7Bit", "fwrite");
2285 if (ferror (ce
->ce_fp
)) {
2286 content_error (ce
->ce_file
, ct
, "error writing to");
2291 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2293 if (fflush (ce
->ce_fp
)) {
2294 content_error (ce
->ce_file
, ct
, "error writing to");
2298 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2301 *file
= ce
->ce_file
;
2306 return fileno (ce
->ce_fp
);
2309 free_encoding (ct
, 0);
2323 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2325 char cachefile
[BUFSIZ
];
2328 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2333 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2334 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2340 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2341 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2342 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2343 ce
->ce_file
= mh_xstrdup(cachefile
);
2347 admonish (cachefile
, "unable to fopen for reading");
2350 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2354 *file
= ce
->ce_file
;
2355 *fd
= fileno (ce
->ce_fp
);
2366 return init_encoding (ct
, openFile
);
2371 openFile (CT ct
, char **file
)
2374 char cachefile
[BUFSIZ
];
2375 struct exbody
*e
= ct
->c_ctexbody
;
2376 CE ce
= &ct
->c_cefile
;
2378 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2390 content_error (NULL
, ct
, "missing name parameter");
2394 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2397 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2398 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2402 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2403 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2404 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2408 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2409 if ((fp
= fopen (cachefile
, "w"))) {
2411 char buffer
[BUFSIZ
];
2412 FILE *gp
= ce
->ce_fp
;
2414 fseek (gp
, 0L, SEEK_SET
);
2416 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2418 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2419 advise ("openFile", "fwrite");
2424 admonish (ce
->ce_file
, "error reading");
2425 (void) m_unlink (cachefile
);
2426 } else if (ferror (fp
)) {
2427 admonish (cachefile
, "error writing");
2428 (void) m_unlink (cachefile
);
2435 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2436 *file
= ce
->ce_file
;
2437 return fileno (ce
->ce_fp
);
2447 return init_encoding (ct
, openFTP
);
2452 openFTP (CT ct
, char **file
)
2458 char *bp
, *ftp
, *user
, *pass
;
2459 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2461 CE ce
= &ct
->c_cefile
;
2462 static char *username
= NULL
;
2463 static char *password
= NULL
;
2467 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2473 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2484 if (!e
->eb_name
|| !e
->eb_site
) {
2485 content_error (NULL
, ct
, "missing %s parameter",
2486 e
->eb_name
? "site": "name");
2490 /* Get the buffer ready to go */
2492 buflen
= sizeof(buffer
);
2495 * Construct the query message for user
2497 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2503 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2509 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2510 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2515 if (e
->eb_size
> 0) {
2516 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2521 snprintf (bp
, buflen
, "? ");
2524 * Now, check the answer
2526 if (!read_yes_or_no_if_tty (buffer
))
2531 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2535 ruserpass (e
->eb_site
, &username
, &password
, 0);
2540 ce
->ce_unlink
= (*file
== NULL
);
2542 cachefile
[0] = '\0';
2543 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2544 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2545 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2546 if (*file
== NULL
) {
2553 ce
->ce_file
= mh_xstrdup(*file
);
2555 ce
->ce_file
= mh_xstrdup(cachefile
);
2558 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2559 die("unable to create temporary file in %s",
2562 ce
->ce_file
= mh_xstrdup(tempfile
);
2565 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2566 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2575 vec
[vecp
++] = r1bindex (ftp
, '/');
2576 vec
[vecp
++] = e
->eb_site
;
2579 vec
[vecp
++] = e
->eb_dir
;
2580 vec
[vecp
++] = e
->eb_name
;
2581 vec
[vecp
++] = ce
->ce_file
,
2582 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2583 ? "ascii" : "binary";
2591 adios ("fork", "unable to");
2595 close (fileno (ce
->ce_fp
));
2597 fprintf (stderr
, "unable to exec ");
2603 if (pidXwait (child_id
, NULL
)) {
2604 username
= password
= NULL
;
2614 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2619 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2620 if ((fp
= fopen (cachefile
, "w"))) {
2622 FILE *gp
= ce
->ce_fp
;
2624 fseek (gp
, 0L, SEEK_SET
);
2626 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2628 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2629 advise ("openFTP", "fwrite");
2634 admonish (ce
->ce_file
, "error reading");
2635 (void) m_unlink (cachefile
);
2636 } else if (ferror (fp
)) {
2637 admonish (cachefile
, "error writing");
2638 (void) m_unlink (cachefile
);
2646 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2647 *file
= ce
->ce_file
;
2648 return fileno (ce
->ce_fp
);
2659 return init_encoding (ct
, openMail
);
2664 openMail (CT ct
, char **file
)
2666 int child_id
, fd
, vecp
;
2668 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2669 struct exbody
*e
= ct
->c_ctexbody
;
2670 CE ce
= &ct
->c_cefile
;
2672 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2683 if (!e
->eb_server
) {
2684 content_error (NULL
, ct
, "missing server parameter");
2688 /* Get buffer ready to go */
2690 buflen
= sizeof(buffer
);
2692 /* Now, construct query message */
2693 snprintf (bp
, buflen
, "Retrieve content");
2699 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2705 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2707 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2709 /* Now, check answer */
2710 if (!read_yes_or_no_if_tty (buffer
))
2714 vec
[vecp
++] = r1bindex (mailproc
, '/');
2715 vec
[vecp
++] = e
->eb_server
;
2716 vec
[vecp
++] = "-subject";
2717 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2718 vec
[vecp
++] = "-body";
2719 vec
[vecp
++] = e
->eb_body
;
2725 advise ("fork", "unable to");
2729 execvp (mailproc
, vec
);
2730 fprintf (stderr
, "unable to exec ");
2736 if (pidXwait (child_id
, NULL
) == OK
)
2737 inform("request sent");
2741 if (*file
== NULL
) {
2743 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2744 die("unable to create temporary file in %s",
2747 ce
->ce_file
= mh_xstrdup(tempfile
);
2750 ce
->ce_file
= mh_xstrdup(*file
);
2754 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2755 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2759 /* showproc is for mhshow and mhstore, though mhlist -debug
2760 * prints it, too. */
2761 free(ct
->c_showproc
);
2762 ct
->c_showproc
= mh_xstrdup("true");
2764 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2765 *file
= ce
->ce_file
;
2766 return fileno (ce
->ce_fp
);
2777 return init_encoding (ct
, openURL
);
2782 openURL (CT ct
, char **file
)
2784 struct exbody
*e
= ct
->c_ctexbody
;
2785 CE ce
= &ct
->c_cefile
;
2786 char *urlprog
, *program
;
2787 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2791 struct msgs_array args
= { 0, 0, NULL
};
2794 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2798 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2802 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2814 content_error(NULL
, ct
, "missing url parameter");
2818 ce
->ce_unlink
= (*file
== NULL
);
2820 cachefile
[0] = '\0';
2822 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2823 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2824 if (*file
== NULL
) {
2831 ce
->ce_file
= mh_xstrdup(*file
);
2833 ce
->ce_file
= mh_xstrdup(cachefile
);
2836 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2837 die("unable to create temporary file in %s",
2840 ce
->ce_file
= mh_xstrdup(tempfile
);
2843 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2844 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2848 switch (child_id
= fork()) {
2850 adios ("fork", "unable to");
2854 argsplit_msgarg(&args
, urlprog
, &program
);
2855 app_msgarg(&args
, e
->eb_url
);
2856 app_msgarg(&args
, NULL
);
2857 dup2(fileno(ce
->ce_fp
), 1);
2858 close(fileno(ce
->ce_fp
));
2859 execvp(program
, args
.msgs
);
2860 fprintf(stderr
, "Unable to exec ");
2866 if (pidXwait(child_id
, NULL
)) {
2874 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2879 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2880 if ((fp
= fopen(cachefile
, "w"))) {
2882 FILE *gp
= ce
->ce_fp
;
2884 fseeko(gp
, 0, SEEK_SET
);
2886 while ((cc
= fread(buffer
, sizeof(*buffer
),
2887 sizeof(buffer
), gp
)) > 0)
2888 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2889 advise ("openURL", "fwrite");
2895 admonish(ce
->ce_file
, "error reading");
2896 (void) m_unlink (cachefile
);
2903 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2904 *file
= ce
->ce_file
;
2905 return fileno(ce
->ce_fp
);
2910 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2911 * has to be base64 decoded.
2914 readDigest (CT ct
, char *cp
)
2916 unsigned char *digest
;
2919 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2920 const size_t maxlen
= sizeof ct
->c_digest
;
2922 if (strlen ((char *) digest
) <= maxlen
) {
2923 memcpy (ct
->c_digest
, digest
, maxlen
);
2928 fprintf (stderr
, "MD5 digest=");
2929 for (i
= 0; i
< maxlen
; ++i
) {
2930 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2932 fprintf (stderr
, "\n");
2938 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2939 (int) strlen ((char *) digest
));
2949 /* Multipart parts might have content before the first subpart and/or
2950 after the last subpart that hasn't been stored anywhere else, so do
2953 get_leftover_mp_content (CT ct
, int before
/* or after */)
2955 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2957 bool found_boundary
= false;
2963 char *content
= NULL
;
2965 if (! m
) return NOTOK
;
2968 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2970 /* Isolate the beginning of this part to the beginning of the
2971 first subpart and save any content between them. */
2972 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2973 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2974 boundary
= concat ("--", m
->mp_start
, NULL
);
2976 struct part
*last_subpart
= NULL
;
2977 struct part
*subpart
;
2979 /* Go to the last subpart to get its end position. */
2980 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2981 last_subpart
= subpart
;
2984 if (last_subpart
== NULL
) return NOTOK
;
2986 /* Isolate the end of the last subpart to the end of this part
2987 and save any content between them. */
2988 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2989 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2990 boundary
= concat ("--", m
->mp_stop
, NULL
);
2993 /* Back up by 1 to pick up the newline. */
2994 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2996 /* Don't look beyond beginning of first subpart (before) or
2997 next part (after). */
2998 if (read
> max
) bufp
[read
-max
] = '\0';
3001 if (! strcmp (bufp
, boundary
)) {
3002 found_boundary
= true;
3005 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3006 found_boundary
= true;
3011 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3013 char *old_content
= content
;
3014 content
= concat (content
, bufp
, NULL
);
3018 ? concat ("\n", bufp
, NULL
)
3019 : concat (bufp
, NULL
);
3024 if (found_boundary
|| read
> max
) break;
3026 if (read
> max
) break;
3030 /* Skip the newline if that's all there is. */
3034 /* Remove trailing newline, except at EOF. */
3035 if ((before
|| ! feof (ct
->c_fp
)) &&
3036 (cp
= content
+ strlen (content
)) > content
&&
3041 if (strlen (content
) > 1) {
3043 m
->mp_content_before
= content
;
3045 m
->mp_content_after
= content
;
3060 ct_type_str (int type
)
3063 case CT_APPLICATION
:
3064 return "application";
3080 return "unknown_type";
3086 ct_subtype_str (int type
, int subtype
)
3089 case CT_APPLICATION
:
3091 case APPLICATION_OCTETS
:
3093 case APPLICATION_POSTSCRIPT
:
3094 return "postscript";
3096 return "unknown_app_subtype";
3100 case MESSAGE_RFC822
:
3102 case MESSAGE_PARTIAL
:
3104 case MESSAGE_EXTERNAL
:
3107 return "unknown_msg_subtype";
3113 case MULTI_ALTERNATE
:
3114 return "alternative";
3117 case MULTI_PARALLEL
:
3122 return "unknown_multipart_subtype";
3133 return "unknown_text_subtype";
3136 return "unknown_type";
3142 ct_str_type (const char *type
)
3144 struct str2init
*s2i
;
3146 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3147 if (! strcasecmp (type
, s2i
->si_key
)) {
3151 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3160 ct_str_subtype (int type
, const char *subtype
)
3165 case CT_APPLICATION
:
3166 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3167 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3171 return kv
->kv_value
;
3173 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3174 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3178 return kv
->kv_value
;
3180 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3181 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3185 return kv
->kv_value
;
3187 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3188 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3192 return kv
->kv_value
;
3199 /* Find the content type and InitFunc for the CT. */
3200 const struct str2init
*
3201 get_ct_init (int type
)
3203 const struct str2init
*sp
;
3205 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3206 if (type
== sp
->si_val
) {
3215 ce_str (int encoding
)
3221 return "quoted-printable";
3237 /* Find the content type and InitFunc for the content encoding method. */
3238 const struct str2init
*
3239 get_ce_method (const char *method
)
3241 struct str2init
*sp
;
3243 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3244 if (! strcasecmp (method
, sp
->si_key
)) {
3253 * Parse a series of MIME attributes (or parameters) given a header as
3256 * Arguments include:
3258 * filename - Name of input file (for error messages)
3259 * fieldname - Name of field being processed
3260 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3261 * Updated to point to end of attributes when finished.
3262 * param_head - Pointer to head of parameter list
3263 * param_tail - Pointer to tail of parameter list
3264 * commentp - Pointer to header comment pointer (may be NULL)
3266 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3267 * DONE to indicate a benign error (minor parsing error, but the program
3272 parse_header_attrs (const char *filename
, const char *fieldname
,
3273 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3276 char *cp
= *header_attrp
;
3282 struct sectlist
*next
;
3288 struct sectlist
*sechead
;
3289 struct parmlist
*next
;
3290 } *pp
, *pp2
, *phead
= NULL
;
3292 while (*cp
== ';') {
3293 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3294 bool encoded
= false;
3295 bool partial
= false;
3296 int len
= 0, index
= 0;
3299 while (isspace ((unsigned char) *cp
))
3303 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3308 if (! suppress_extraneous_trailing_semicolon_warning
) {
3309 inform("extraneous trailing ';' in message %s's %s: "
3310 "parameter list", filename
, fieldname
);
3315 /* down case the attribute name */
3316 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3317 *dp
= tolower ((unsigned char) *dp
);
3319 for (up
= dp
; isspace ((unsigned char) *dp
);)
3321 if (dp
== cp
|| *dp
!= '=') {
3322 inform("invalid parameter in message %s's %s: field\n"
3323 " parameter %s (error detected at offset %ld)",
3324 filename
, fieldname
, cp
, (long)(dp
- cp
));
3329 * To handle RFC 2231, we have to deal with the following extensions:
3331 * name*=encoded-value
3332 * name*<N>=part-N-of-a-parameter-value
3333 * name*<N>*=encoded-part-N-of-a-parameter-value
3336 * If there's a * right before the equal sign, it's encoded.
3337 * If there's a * and one or more digits, then it's section N.
3339 * Remember we can have one or the other, or both. cp points to
3340 * beginning of name, up points past the last character in the
3344 for (vp
= cp
; vp
< up
; vp
++) {
3345 if (*vp
== '*' && vp
< up
- 1) {
3349 if (*vp
== '*' && vp
== up
- 1) {
3351 } else if (partial
) {
3352 if (isdigit((unsigned char) *vp
))
3353 index
= *vp
- '0' + index
* 10;
3355 inform("invalid parameter index in message %s's %s: field"
3356 "\n (parameter %s)", filename
, fieldname
, cp
);
3365 * Break out the parameter name and value sections and allocate
3369 nameptr
= mh_xmalloc(len
+ 1);
3370 strncpy(nameptr
, cp
, len
);
3371 nameptr
[len
] = '\0';
3373 for (dp
++; isspace ((unsigned char) *dp
);)
3378 * Single quotes delimit the character set and language tag.
3379 * They are required on the first section (or a complete
3384 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3390 charset
= mh_xmalloc(len
+ 1);
3391 strncpy(charset
, dp
, len
);
3392 charset
[len
] = '\0';
3398 inform("missing charset in message %s's %s: field\n"
3399 " (parameter %s)", filename
, fieldname
, nameptr
);
3405 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3412 lang
= mh_xmalloc(len
+ 1);
3413 strncpy(lang
, dp
, len
);
3420 inform("missing language tag in message %s's %s: field\n"
3421 " (parameter %s)", filename
, fieldname
, nameptr
);
3431 * At this point vp should be pointing at the beginning
3432 * of the encoded value/section. Continue until we reach
3433 * the end or get whitespace. But first, calculate the
3434 * length so we can allocate the correct buffer size.
3437 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3439 if (*(vp
+ 1) == '\0' ||
3440 !isxdigit((unsigned char) *(vp
+ 1)) ||
3441 *(vp
+ 2) == '\0' ||
3442 !isxdigit((unsigned char) *(vp
+ 2))) {
3443 inform("invalid encoded sequence in message %s's %s: field\n"
3444 " (parameter %s)", filename
, fieldname
, nameptr
);
3455 up
= valptr
= mh_xmalloc(len
+ 1);
3457 for (vp
= dp
; istoken(*vp
); vp
++) {
3459 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3470 * A "normal" string. If it's got a leading quote, then we
3471 * strip the quotes out. Otherwise go until we reach the end
3472 * or get whitespace. Note we scan it twice; once to get the
3473 * length, then the second time copies it into the destination
3480 for (cp
= dp
+ 1;;) {
3484 inform("invalid quoted-string in message %s's %s: field\n"
3485 " (parameter %s)", filename
, fieldname
, nameptr
);
3505 for (cp
= dp
; istoken (*cp
); cp
++) {
3510 valptr
= mh_xmalloc(len
+ 1);
3514 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3522 strncpy(valptr
, cp
= dp
, len
);
3530 * If 'partial' is set, we don't allocate a parameter now. We
3531 * put it on the parameter linked list to be reassembled later.
3533 * "phead" points to a list of all parameters we need to reassemble.
3534 * Each parameter has a list of sections. We insert the sections in
3539 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3540 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3555 * Insert this into the section linked list
3563 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3564 sp
->next
= pp
->sechead
;
3567 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3568 if (sp2
->index
== sp
->index
) {
3569 inform("duplicate index (%d) in message %s's %s: field"
3570 "\n (parameter %s)", sp
->index
, filename
,
3571 fieldname
, nameptr
);
3574 if (sp2
->index
< sp
->index
&&
3575 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3576 sp
->next
= sp2
->next
;
3583 inform("Internal error: cannot insert partial param "
3584 "in message %s's %s: field\n (parameter %s)",
3585 filename
, fieldname
, nameptr
);
3591 * Save our charset and lang tags.
3594 if (index
== 0 && encoded
) {
3596 pp
->charset
= charset
;
3601 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3602 pm
->pm_charset
= charset
;
3606 while (isspace ((unsigned char) *cp
))
3610 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3616 * Now that we're done, reassemble all of the partial parameters.
3619 for (pp
= phead
; pp
!= NULL
; ) {
3623 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3624 if (sp
->index
!= pindex
++) {
3625 inform("missing section %d for parameter in message "
3626 "%s's %s: field\n (parameter %s)", pindex
- 1,
3627 filename
, fieldname
, pp
->name
);
3633 p
= q
= mh_xmalloc(tlen
+ 1);
3634 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3635 memcpy(q
, sp
->value
, sp
->len
);
3645 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3646 pm
->pm_charset
= pp
->charset
;
3647 pm
->pm_lang
= pp
->lang
;
3658 * Return the charset for a particular content type.
3662 content_charset (CT ct
)
3664 char *ret_charset
= NULL
;
3666 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3668 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3673 * Create a string based on a list of output parameters. Assume that this
3674 * parameter string will be appended to an existing header, so start out
3675 * with the separator (;). Perform RFC 2231 encoding when necessary.
3679 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3681 char *paramout
= NULL
;
3682 char line
[CPERLIN
* 2], *q
;
3683 int curlen
, index
, cont
, encode
, i
;
3684 size_t valoff
, numchars
;
3686 while (params
!= NULL
) {
3692 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3695 if (strlen(params
->pm_name
) > CPERLIN
) {
3696 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3701 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3704 * Loop until we get a parameter that fits within a line. We
3705 * assume new lines start with a tab, so check our overflow based
3715 * At this point we're definitely continuing the line, so
3716 * be sure to include the parameter name and section index.
3719 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3720 params
->pm_name
, index
);
3723 * Both of these functions do a NUL termination
3727 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3728 numchars
, valoff
, index
);
3730 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3740 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3745 * "line" starts with a ;\n\t, so that doesn't count against
3746 * the length. But add 8 since it starts with a tab; that's
3747 * how we end up with 5.
3750 initialwidth
= strlen(line
) + 5;
3753 * At this point the line should be built, so add it to our
3754 * current output buffer.
3757 paramout
= add(line
, paramout
);
3761 * If this won't fit on the line, start a new one. Save room in
3762 * case we need a semicolon on the end
3765 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3777 * At this point, we're either finishing a continued parameter, or
3778 * we're working on a new one.
3782 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3783 params
->pm_name
, index
);
3785 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3790 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3791 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3793 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3794 strlen(params
->pm_value
+ valoff
), valoff
);
3801 paramout
= add(line
, paramout
);
3802 initialwidth
+= strlen(line
);
3804 params
= params
->pm_next
;
3808 *offsetout
= initialwidth
;
3814 * Calculate the size of a parameter.
3818 * pm - The parameter being output
3819 * index - If continuing the parameter, the index of the section
3821 * valueoff - The current offset into the parameter value that we're
3822 * working on (previous sections have consumed valueoff bytes).
3823 * encode - Set if we should perform encoding on this parameter section
3824 * (given that we're consuming bytesfit bytes).
3825 * cont - Set if the remaining data in value will not fit on a single
3826 * line and will need to be continued.
3827 * bytesfit - The number of bytes that we can consume from the parameter
3828 * value and still fit on a completely new line. The
3829 * calculation assumes the new line starts with a tab,
3830 * includes the parameter name and any encoding, and fits
3831 * within CPERLIN bytes. Will always be at least 1.
3835 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3838 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3839 size_t len
= 0, fit
= 0;
3840 int fitlimit
= 0, eightbit
, maxfit
;
3845 * Add up the length. First, start with the parameter name.
3848 len
= strlen(pm
->pm_name
);
3851 * Scan the parameter value and see if we need to do encoding for this
3855 eightbit
= contains8bit(start
, NULL
);
3858 * Determine if we need to encode this section. Encoding is necessary if:
3860 * - There are any 8-bit characters at all and we're on the first
3862 * - There are 8-bit characters within N bytes of our section start.
3863 * N is calculated based on the number of bytes it would take to
3864 * reach CPERLIN. Specifically:
3865 * 8 (starting tab) +
3866 * strlen(param name) +
3867 * 4 ('* for section marker, '=', opening/closing '"')
3869 * is the number of bytes used by everything that isn't part of the
3870 * value. So that gets subtracted from CPERLIN.
3873 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3874 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3875 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3879 len
++; /* Add in equal sign */
3883 * We're using maxfit as a marker for how many characters we can
3884 * fit into the line. Bump it by two because we're not using quotes
3891 * If we don't have a charset or language tag in this parameter,
3895 if (! pm
->pm_charset
) {
3896 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3897 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3898 die("8-bit characters in parameter \"%s\", but "
3899 "local character set is US-ASCII", pm
->pm_name
);
3902 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3904 len
++; /* For the encoding marker */
3907 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3912 * We know we definitely need to include an index. maxfit already
3913 * includes the section marker.
3915 len
+= strlen(indexchar
);
3917 for (p
= start
; *p
!= '\0'; p
++) {
3918 if (isparamencode(*p
)) {
3926 * Just so there's no confusion: maxfit is counting OUTPUT
3927 * characters (post-encoding). fit is counting INPUT characters.
3929 if (! fitlimit
&& maxfit
>= 0)
3931 else if (! fitlimit
)
3936 * Calculate the string length, but add room for quoting \
3937 * and " if necessary. Also account for quotes at beginning
3940 for (p
= start
; *p
!= '\0'; p
++) {
3951 if (! fitlimit
&& maxfit
>= 0)
3953 else if (! fitlimit
)
3970 * Output an encoded parameter string.
3974 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3975 size_t valueoff
, int index
)
3977 size_t outlen
= 0, n
;
3978 char *endptr
= output
+ len
, *p
;
3981 * First, output the marker for an encoded string.
3989 * If the index is 0, output the character set and language tag.
3990 * If theses were NULL, they should have already been filled in
3995 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3999 if (output
> endptr
) {
4000 inform("Internal error: parameter buffer overflow");
4006 * Copy over the value, encoding if necessary
4009 p
= pm
->pm_value
+ valueoff
;
4010 while (valuelen
-- > 0) {
4011 if (isparamencode(*p
)) {
4012 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4019 if (output
> endptr
) {
4020 inform("Internal error: parameter buffer overflow");
4031 * Output a "normal" parameter, without encoding. Be sure to escape
4032 * quotes and backslashes if necessary.
4036 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4040 char *endptr
= output
+ len
, *p
;
4046 p
= pm
->pm_value
+ valueoff
;
4048 while (valuelen
-- > 0) {
4059 if (output
> endptr
) {
4060 inform("Internal error: parameter buffer overflow");
4065 if (output
- 2 > endptr
) {
4066 inform("Internal error: parameter buffer overflow");
4077 * Add a parameter to the parameter linked list
4081 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4086 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4087 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4090 (*last
)->pm_next
= pm
;
4101 * Either replace a current parameter with a new value, or add the parameter
4102 * to the parameter linked list.
4106 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4110 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4111 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4113 * If nocopy is set, it's assumed that we own both name
4114 * and value. We don't need name, so we discard it now.
4119 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4124 return add_param(first
, last
, name
, value
, nocopy
);
4128 * Retrieve a parameter value from a parameter linked list. If the parameter
4129 * value needs converted to the local character set, do that now.
4133 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4135 while (first
!= NULL
) {
4136 if (strcasecmp(name
, first
->pm_name
) == 0) {
4138 return first
->pm_value
;
4139 return getcpy(get_param_value(first
, replace
));
4141 first
= first
->pm_next
;
4148 * Return a parameter value, converting to the local character set if
4153 get_param_value(PM pm
, char replace
)
4155 static char buffer
[4096]; /* I hope no parameters are larger */
4156 size_t bufsize
= sizeof(buffer
);
4161 ICONV_CONST
char *p
;
4162 #else /* HAVE_ICONV */
4164 #endif /* HAVE_ICONV */
4169 * If we don't have a character set indicated, it's assumed to be
4170 * US-ASCII. If it matches our character set, we don't need to convert
4174 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4175 strlen(pm
->pm_charset
))) {
4176 return pm
->pm_value
;
4180 * In this case, we need to convert. If we have iconv support, use
4181 * that. Otherwise, go through and simply replace every non-ASCII
4182 * character with the substitution character.
4187 bufsize
= sizeof(buffer
);
4188 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4190 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4191 if (cd
== (iconv_t
) -1) {
4195 inbytes
= strlen(pm
->pm_value
);
4199 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4200 if (errno
!= EILSEQ
) {
4205 * Reset shift state, substitute our character,
4206 * try to restart conversion.
4209 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4222 for (++p
, --inbytes
;
4223 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4242 #endif /* HAVE_ICONV */
4245 * Take everything non-ASCII and substitute the replacement character
4249 bufsize
= sizeof(buffer
);
4250 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4251 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))