1 /* mhparse.c -- routines to parse the contents of MIME messages
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
14 #include <h/mhparse.h>
17 #include <h/mhcachesbr.h>
18 #include "sbr/m_mktemp.h"
22 #endif /* HAVE_ICONV */
23 #include "sbr/base64.h"
28 int checksw
= 0; /* check Content-MD5 field */
31 * These are for mhfixmsg to:
32 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
34 * 2) Suppress the warning about bogus multipart content, and report it.
35 * 3) Suppress the warning about extraneous trailing ';' in header parameter
38 bool skip_mp_cte_check
;
39 bool suppress_bogus_mp_content_warning
;
40 bool bogus_mp_content
;
41 bool suppress_extraneous_trailing_semicolon_warning
;
44 * By default, suppress warning about multiple MIME-Version header fields.
46 bool suppress_multiple_mime_version_warning
= true;
48 /* list of preferred type/subtype pairs, for -prefer */
49 mime_type_subtype mime_preference
[NPREFS
];
54 * Structures for TEXT messages
56 struct k2v SubText
[] = {
57 { "plain", TEXT_PLAIN
},
58 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
59 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
60 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
63 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
66 * Structures for MULTIPART messages
68 struct k2v SubMultiPart
[] = {
69 { "mixed", MULTI_MIXED
},
70 { "alternative", MULTI_ALTERNATE
},
71 { "digest", MULTI_DIGEST
},
72 { "parallel", MULTI_PARALLEL
},
73 { "related", MULTI_RELATED
},
74 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
78 * Structures for MESSAGE messages
80 struct k2v SubMessage
[] = {
81 { "rfc822", MESSAGE_RFC822
},
82 { "partial", MESSAGE_PARTIAL
},
83 { "external-body", MESSAGE_EXTERNAL
},
84 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
88 * Structure for APPLICATION messages
90 struct k2v SubApplication
[] = {
91 { "octet-stream", APPLICATION_OCTETS
},
92 { "postscript", APPLICATION_POSTSCRIPT
},
93 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
97 * Mapping of names of CTE types in mhbuild directives
99 static struct k2v EncodingType
[] = {
102 { "q-p", CE_QUOTED
},
103 { "quoted-printable", CE_QUOTED
},
104 { "b64", CE_BASE64
},
105 { "base64", CE_BASE64
},
113 static CT
get_content (FILE *, char *, int);
114 static int get_comment (const char *, const char *, char **, char **);
116 static int InitGeneric (CT
);
117 static int InitText (CT
);
118 static int InitMultiPart (CT
);
119 static void reverse_parts (CT
);
120 static void prefer_parts(CT ct
);
121 static int InitMessage (CT
);
122 static int InitApplication (CT
);
123 static int init_encoding (CT
, OpenCEFunc
);
124 static unsigned long size_encoding (CT
);
125 static int InitBase64 (CT
);
126 static int openBase64 (CT
, char **);
127 static int InitQuoted (CT
);
128 static int openQuoted (CT
, char **);
129 static int Init7Bit (CT
);
130 static int openExternal (CT
, CT
, CE
, char **, int *);
131 static int InitFile (CT
);
132 static int openFile (CT
, char **);
133 static int InitFTP (CT
);
134 static int openFTP (CT
, char **);
135 static int InitMail (CT
);
136 static int openMail (CT
, char **);
137 static int readDigest (CT
, char *);
138 static int get_leftover_mp_content (CT
, int);
139 static int InitURL (CT
);
140 static int openURL (CT
, char **);
141 static int parse_header_attrs (const char *, const char *, char **, PM
*,
143 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
144 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
145 static int get_dispo (char *, CT
, int);
147 struct str2init str2cts
[] = {
148 { "application", CT_APPLICATION
, InitApplication
},
149 { "audio", CT_AUDIO
, InitGeneric
},
150 { "image", CT_IMAGE
, InitGeneric
},
151 { "message", CT_MESSAGE
, InitMessage
},
152 { "multipart", CT_MULTIPART
, InitMultiPart
},
153 { "text", CT_TEXT
, InitText
},
154 { "video", CT_VIDEO
, InitGeneric
},
155 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
156 { NULL
, CT_UNKNOWN
, NULL
},
159 struct str2init str2ces
[] = {
160 { "base64", CE_BASE64
, InitBase64
},
161 { "quoted-printable", CE_QUOTED
, InitQuoted
},
162 { "8bit", CE_8BIT
, Init7Bit
},
163 { "7bit", CE_7BIT
, Init7Bit
},
164 { "binary", CE_BINARY
, Init7Bit
},
165 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
166 { NULL
, CE_UNKNOWN
, NULL
},
170 * NOTE WELL: si_key MUST NOT have value of NOTOK
172 * si_val is 1 if access method is anonymous.
174 struct str2init str2methods
[] = {
175 { "afs", 1, InitFile
},
176 { "anon-ftp", 1, InitFTP
},
177 { "ftp", 0, InitFTP
},
178 { "local-file", 0, InitFile
},
179 { "mail-server", 0, InitMail
},
180 { "url", 0, InitURL
},
186 * Main entry point for parsing a MIME message or file.
187 * It returns the Content structure for the top level
188 * entity in the file.
192 parse_mime (char *file
)
201 bogus_mp_content
= false;
204 * Check if file is actually standard input
206 if ((is_stdin
= !(strcmp (file
, "-")))) {
207 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
209 advise("mhparse", "unable to create temporary file in %s",
213 file
= mh_xstrdup(tfile
);
215 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
216 if (fwrite(buffer
, 1, n
, fp
) != n
) {
217 (void) m_unlink (file
);
218 advise (file
, "error copying to temporary file");
224 if (ferror (stdin
)) {
225 (void) m_unlink (file
);
226 advise ("stdin", "error reading");
230 (void) m_unlink (file
);
231 advise (file
, "error writing");
234 fseek (fp
, 0L, SEEK_SET
);
235 } else if (stat (file
, &statbuf
) == NOTOK
) {
236 advise (file
, "unable to stat");
238 } else if (S_ISDIR(statbuf
.st_mode
)) {
239 /* Don't try to parse a directory. */
240 inform("%s is a directory", file
);
242 } else if ((fp
= fopen (file
, "r")) == NULL
) {
243 advise (file
, "unable to read");
247 if (!(ct
= get_content (fp
, file
, 1))) {
249 (void) m_unlink (file
);
250 inform("unable to decode %s", file
);
255 ct
->c_unlink
= 1; /* temp file to remove */
259 if (ct
->c_end
== 0L) {
260 fseek (fp
, 0L, SEEK_END
);
261 ct
->c_end
= ftell (fp
);
264 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
276 * Main routine for reading/parsing the headers
277 * of a message content.
279 * toplevel = 1 # we are at the top level of the message
280 * toplevel = 0 # we are inside message type or multipart type
281 * # other than multipart/digest
282 * toplevel = -1 # we are inside multipart/digest
283 * NB: on failure we will fclose(in)!
287 get_content (FILE *in
, char *file
, int toplevel
)
290 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
294 m_getfld_state_t gstate
;
296 /* allocate the content structure */
299 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
300 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
303 * Parse the header fields for this
304 * content into a linked list.
306 gstate
= m_getfld_state_init(in
);
307 m_getfld_track_filepos2(&gstate
);
308 for (compnum
= 1;;) {
309 int bufsz
= sizeof buf
;
310 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
315 /* get copies of the buffers */
316 np
= mh_xstrdup(name
);
317 vp
= mh_xstrdup(buf
);
319 /* if necessary, get rest of field */
320 while (state
== FLDPLUS
) {
322 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
323 vp
= add (buf
, vp
); /* add to previous value */
326 /* Now add the header data to the list */
327 add_header (ct
, np
, vp
);
329 /* continue, to see if this isn't the last header field */
330 ct
->c_begin
= ftell (in
) + 1;
334 /* There are two cases. The unusual one is when there is no
335 * blank line between the headers and the body. This is
336 * indicated by the name of the header starting with `:'.
338 * For both cases, normal first, `1' is the desired c_begin
339 * file position for the start of the body, and `2' is the
340 * file position when buf is returned.
342 * f o o : b a r \n \n b o d y \n bufsz = 6
344 * f o o : b a r \n b o d y \n bufsz = 4
347 * For the normal case, bufsz includes the
348 * header-terminating `\n', even though it is not in buf,
349 * but bufsz isn't affected when it's missing in the unusual
351 if (name
[0] == ':') {
352 ct
->c_begin
= ftell(in
) - bufsz
;
354 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
359 ct
->c_begin
= ftell (in
);
364 adios (NULL
, "message format error in component #%d", compnum
);
367 adios (NULL
, "getfld() returned %d", state
);
370 /* break out of the loop */
373 m_getfld_state_destroy (&gstate
);
376 * Read the content headers. We will parse the
377 * MIME related header fields into their various
378 * structures and set internal flags related to
379 * content type/subtype, etc.
382 hp
= ct
->c_first_hf
; /* start at first header field */
384 /* Get MIME-Version field */
385 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
390 vrsn
= mh_xstrdup(FENDNULL(hp
->value
));
392 /* Now, cleanup this field */
395 while (isspace ((unsigned char) *cp
))
397 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
399 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
400 if (!isspace ((unsigned char) *dp
))
404 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
407 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
410 for (dp
= cp
; istoken (*dp
); dp
++)
414 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
417 inform("message %s has unknown value for %s: field (%s), continuing...",
418 ct
->c_file
, VRSN_FIELD
, cp
);
423 if (! suppress_multiple_mime_version_warning
)
424 inform("message %s has multiple %s: fields",
425 ct
->c_file
, VRSN_FIELD
);
429 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
430 /* Get Content-Type field */
431 struct str2init
*s2i
;
432 CI ci
= &ct
->c_ctinfo
;
434 /* Check if we've already seen a Content-Type header */
436 inform("message %s has multiple %s: fields",
437 ct
->c_file
, TYPE_FIELD
);
441 /* Parse the Content-Type field */
442 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
446 * Set the Init function and the internal
447 * flag for this content type.
449 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
450 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
452 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
454 ct
->c_type
= s2i
->si_val
;
455 ct
->c_ctinitfnx
= s2i
->si_init
;
457 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
458 /* Get Content-Transfer-Encoding field */
460 struct str2init
*s2i
;
463 * Check if we've already seen the
464 * Content-Transfer-Encoding field
467 inform("message %s has multiple %s: fields",
468 ct
->c_file
, ENCODING_FIELD
);
472 /* get copy of this field */
473 ct
->c_celine
= cp
= mh_xstrdup(FENDNULL(hp
->value
));
475 while (isspace ((unsigned char) *cp
))
477 for (dp
= cp
; istoken (*dp
); dp
++)
483 * Find the internal flag and Init function
484 * for this transfer encoding.
486 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
487 if (!strcasecmp (cp
, s2i
->si_key
))
489 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
492 ct
->c_encoding
= s2i
->si_val
;
494 /* Call the Init function for this encoding */
495 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
498 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
499 /* Get Content-MD5 field */
505 if (ct
->c_digested
) {
506 inform("message %s has multiple %s: fields",
507 ct
->c_file
, MD5_FIELD
);
511 ep
= cp
= mh_xstrdup(FENDNULL(hp
->value
)); /* get a copy */
513 while (isspace ((unsigned char) *cp
))
515 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
517 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
518 if (!isspace ((unsigned char) *dp
))
522 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
525 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
530 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
538 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
539 /* Get Content-ID field */
540 ct
->c_id
= add (hp
->value
, ct
->c_id
);
542 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
543 /* Get Content-Description field */
544 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
546 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
547 /* Get Content-Disposition field */
548 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
553 hp
= hp
->next
; /* next header field */
557 * Check if we saw a Content-Type field.
558 * If not, then assign a default value for
559 * it, and the Init function.
563 * If we are inside a multipart/digest message,
564 * so default type is message/rfc822
567 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
569 ct
->c_type
= CT_MESSAGE
;
570 ct
->c_ctinitfnx
= InitMessage
;
573 * Else default type is text/plain
575 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
577 ct
->c_type
= CT_TEXT
;
578 ct
->c_ctinitfnx
= InitText
;
582 /* Use default Transfer-Encoding, if necessary */
584 ct
->c_encoding
= CE_7BIT
;
597 * small routine to add header field to list
601 add_header (CT ct
, char *name
, char *value
)
605 /* allocate header field structure */
608 /* link data into header structure */
613 /* link header structure into the list */
614 if (ct
->c_first_hf
== NULL
) {
615 ct
->c_first_hf
= hp
; /* this is the first */
618 ct
->c_last_hf
->next
= hp
; /* add it to the end */
627 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
628 * directives. Fills in the information of the CTinfo structure.
631 get_ctinfo (char *cp
, CT ct
, int magic
)
640 /* store copy of Content-Type line */
641 cp
= ct
->c_ctline
= mh_xstrdup(FENDNULL(cp
));
643 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
646 /* change newlines to spaces */
647 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
650 /* trim trailing spaces */
651 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
652 if (!isspace ((unsigned char) *dp
))
657 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
659 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
660 &ci
->ci_comment
) == NOTOK
)
663 for (dp
= cp
; istoken (*dp
); dp
++)
667 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
672 inform("invalid %s: field in message %s (empty type)",
673 TYPE_FIELD
, ct
->c_file
);
676 to_lower(ci
->ci_type
);
678 while (isspace ((unsigned char) *cp
))
681 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
682 &ci
->ci_comment
) == NOTOK
)
687 ci
->ci_subtype
= mh_xstrdup("");
692 while (isspace ((unsigned char) *cp
))
695 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
696 &ci
->ci_comment
) == NOTOK
)
699 for (dp
= cp
; istoken (*dp
); dp
++)
703 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
707 if (!*ci
->ci_subtype
) {
708 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
709 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
712 to_lower(ci
->ci_subtype
);
715 while (isspace ((unsigned char) *cp
))
718 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
719 &ci
->ci_comment
) == NOTOK
)
722 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
723 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
724 &ci
->ci_comment
)) != OK
) {
725 return status
== NOTOK
? NOTOK
: OK
;
729 * Get any <Content-Id> given in buffer
731 if (magic
&& *cp
== '<') {
734 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
735 inform("invalid ID in message %s", ct
->c_file
);
741 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
747 while (isspace ((unsigned char) *cp
))
752 * Get any [Content-Description] given in buffer.
754 if (magic
&& *cp
== '[') {
756 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
760 inform("invalid description in message %s", ct
->c_file
);
768 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
774 while (isspace ((unsigned char) *cp
))
779 * Get any {Content-Disposition} given in buffer.
781 if (magic
&& *cp
== '{') {
783 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
787 inform("invalid disposition in message %s", ct
->c_file
);
795 if (get_dispo(cp
, ct
, 1) != OK
)
801 while (isspace ((unsigned char) *cp
))
806 * Get any extension directives (right now just the content transfer
807 * encoding, but maybe others) that we care about.
810 if (magic
&& *cp
== '*') {
812 * See if it's a CTE we match on
817 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
821 inform("invalid null transfer encoding specification");
828 ct
->c_reqencoding
= CE_UNKNOWN
;
830 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
831 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
832 ct
->c_reqencoding
= kv
->kv_value
;
837 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
838 inform("invalid CTE specification: \"%s\"", dp
);
842 while (isspace ((unsigned char) *cp
))
847 * Check if anything is left over
851 ci
->ci_magic
= mh_xstrdup(cp
);
853 /* If there is a Content-Disposition header and it doesn't
854 have a *filename=, extract it from the magic contents.
855 The r1bindex call skips any leading directory
857 if (ct
->c_dispo_type
&&
858 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
859 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
860 r1bindex(ci
->ci_magic
, '/'), 0);
864 inform("extraneous information in message %s's %s: field\n"
865 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
873 * Parse out a Content-Disposition header. A lot of this is cribbed from
877 get_dispo (char *cp
, CT ct
, int buildflag
)
879 char *dp
, *dispoheader
;
884 * Save the whole copy of the Content-Disposition header, unless we're
885 * processing a mhbuild directive. A NULL c_dispo will be a flag to
886 * mhbuild that the disposition header needs to be generated at that
890 dispoheader
= cp
= mh_xstrdup(FENDNULL(cp
));
892 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
895 /* change newlines to spaces */
896 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
899 /* trim trailing spaces */
900 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
901 if (!isspace ((unsigned char) *dp
))
906 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
908 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
914 for (dp
= cp
; istoken (*dp
); dp
++)
918 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
922 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
925 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
926 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
928 if (status
== NOTOK
) {
933 inform("extraneous information in message %s's %s: field\n (%s)",
934 ct
->c_file
, DISPO_FIELD
, cp
);
940 ct
->c_dispo
= dispoheader
;
947 get_comment (const char *filename
, const char *fieldname
, char **ap
,
952 char c
, buffer
[BUFSIZ
], *dp
;
962 inform("invalid comment in message %s's %s: field",
963 filename
, fieldname
);
968 if ((c
= *cp
++) == '\0')
991 if ((dp
= *commentp
)) {
992 *commentp
= concat (dp
, " ", buffer
, NULL
);
995 *commentp
= mh_xstrdup(buffer
);
999 while (isspace ((unsigned char) *cp
))
1010 * Handles content types audio, image, and video.
1011 * There's not much to do right here.
1019 return OK
; /* not much to do here */
1030 char buffer
[BUFSIZ
];
1035 CI ci
= &ct
->c_ctinfo
;
1037 /* check for missing subtype */
1038 if (!*ci
->ci_subtype
)
1039 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1042 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1044 /* allocate text character set structure */
1046 ct
->c_ctparams
= (void *) t
;
1048 /* scan for charset parameter */
1049 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1050 if (!strcasecmp (pm
->pm_name
, "charset"))
1053 /* check if content specified a character set */
1055 chset
= pm
->pm_value
;
1056 t
->tx_charset
= CHARSET_SPECIFIED
;
1058 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1062 * If we can not handle character set natively,
1063 * then check profile for string to modify the
1064 * terminal or display method.
1066 * termproc is for mhshow, though mhlist -debug prints it, too.
1068 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1069 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1070 if ((cp
= context_find (buffer
)))
1071 ct
->c_termproc
= mh_xstrdup(cp
);
1083 InitMultiPart (CT ct
)
1093 struct multipart
*m
;
1094 struct part
*part
, **next
;
1095 CI ci
= &ct
->c_ctinfo
;
1100 * The encoding for multipart messages must be either
1101 * 7bit, 8bit, or binary (per RFC 2045).
1103 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1104 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1105 /* Copy the Content-Transfer-Encoding header field body so we can
1106 remove any trailing whitespace and leading blanks from it. */
1107 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1109 bp
= cte
+ strlen (cte
) - 1;
1110 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1111 for (bp
= cte
; isblank((unsigned char)*bp
); ++bp
) continue;
1113 inform("\"%s/%s\" type in message %s must be encoded in\n"
1114 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1115 "mhfixmsg -fixcte can fix it, or\n"
1116 "manually edit the file and change the \"%s\"\n"
1117 "Content-Transfer-Encoding to one of those. For now, continuing...",
1118 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1125 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1128 * Check for "boundary" parameter, which is
1129 * required for multipart messages.
1132 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1133 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1139 /* complain if boundary parameter is missing */
1141 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1142 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1146 /* allocate primary structure for multipart info */
1148 ct
->c_ctparams
= (void *) m
;
1150 /* check if boundary parameter contains only whitespace characters */
1151 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1154 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1155 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1159 /* remove trailing whitespace from boundary parameter */
1160 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1161 if (!isspace ((unsigned char) *dp
))
1165 /* record boundary separators */
1166 m
->mp_start
= concat (bp
, "\n", NULL
);
1167 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1169 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1170 advise (ct
->c_file
, "unable to open for reading");
1174 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1176 next
= &m
->mp_parts
;
1180 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1185 if (bufp
[0] != '-' || bufp
[1] != '-')
1188 if (strcmp (bufp
+ 2, m
->mp_start
))
1193 next
= &part
->mp_next
;
1195 if (!(p
= get_content (fp
, ct
->c_file
,
1196 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1204 fseek (fp
, pos
, SEEK_SET
);
1207 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1211 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1212 if (p
->c_end
< p
->c_begin
)
1213 p
->c_begin
= p
->c_end
;
1218 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1223 if (! suppress_bogus_mp_content_warning
) {
1224 inform("bogus multipart content in message %s", ct
->c_file
);
1226 bogus_mp_content
= true;
1228 if (!inout
&& part
) {
1230 p
->c_end
= ct
->c_end
;
1232 if (p
->c_begin
>= p
->c_end
) {
1233 for (next
= &m
->mp_parts
; *next
!= part
;
1234 next
= &((*next
)->mp_next
))
1243 /* reverse the order of the parts for multipart/alternative */
1244 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1250 * label all subparts with part number, and
1251 * then initialize the content of the subpart.
1256 char partnam
[BUFSIZ
];
1259 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1260 pp
= partnam
+ strlen (partnam
);
1265 for (part
= m
->mp_parts
, partnum
= 1; part
;
1266 part
= part
->mp_next
, partnum
++) {
1269 sprintf (pp
, "%d", partnum
);
1270 p
->c_partno
= mh_xstrdup(partnam
);
1272 /* initialize the content of the subparts */
1273 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1282 get_leftover_mp_content (ct
, 1);
1283 get_leftover_mp_content (ct
, 0);
1293 * reverse the order of the parts of a multipart/alternative,
1294 * presumably to put the "most favored" alternative first, for
1295 * ease of choosing/displaying it later on. from a mail message on
1296 * nmh-workers, from kenh:
1297 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1298 * see code in mhn that did the same thing... According to the RCS
1299 * logs, that code was around from the initial checkin of mhn.c by
1300 * John Romine in 1992, which is as far back as we have."
1303 reverse_parts (CT ct
)
1305 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1309 /* Reverse the order of its parts by walking the mp_parts list
1310 and pushing each node to the front. */
1311 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1312 next
= part
->mp_next
;
1313 part
->mp_next
= m
->mp_parts
;
1319 move_preferred_part(CT ct
, mime_type_subtype
*pref
)
1321 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1322 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1326 /* move the matching part(s) to the head of the list: walk the
1327 * list of parts, move matching parts to a new list (maintaining
1328 * their order), and finally, concatenate the old list onto the
1335 head
->mp_next
= m
->mp_parts
;
1336 nhead
->mp_next
= NULL
;
1340 part
= head
->mp_next
;
1341 while (part
!= NULL
) {
1342 ci
= &part
->mp_part
->c_ctinfo
;
1343 if (!strcasecmp(ci
->ci_type
, pref
->type
) &&
1345 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1346 prev
->mp_next
= part
->mp_next
;
1347 part
->mp_next
= NULL
;
1348 ntail
->mp_next
= part
;
1350 part
= prev
->mp_next
;
1353 part
= prev
->mp_next
;
1356 ntail
->mp_next
= head
->mp_next
;
1357 m
->mp_parts
= nhead
->mp_next
;
1361 * move parts that match the user's preferences (-prefer) to the head
1362 * of the line. process preferences in reverse so first one given
1363 * ends up first in line
1369 for (i
= 0; i
< npreferred
; i
++)
1370 move_preferred_part(ct
, mime_preference
+ i
);
1375 /* parse_mime() arranges alternates in reverse (priority) order. This
1376 function can be used to reverse them back. This will put, for
1377 example, a text/plain part before a text/html part in a
1378 multipart/alternative part, for example, where it belongs. */
1380 reverse_alternative_parts (CT ct
) {
1381 if (ct
->c_type
== CT_MULTIPART
) {
1382 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1385 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1389 /* And call recursively on each part of a multipart. */
1390 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1391 reverse_alternative_parts (part
->mp_part
);
1404 CI ci
= &ct
->c_ctinfo
;
1406 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1407 inform("\"%s/%s\" type in message %s should be encoded in "
1408 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1413 /* check for missing subtype */
1414 if (!*ci
->ci_subtype
)
1415 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1418 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1420 switch (ct
->c_subtype
) {
1421 case MESSAGE_RFC822
:
1424 case MESSAGE_PARTIAL
:
1430 ct
->c_ctparams
= (void *) p
;
1432 /* scan for parameters "id", "number", and "total" */
1433 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1434 if (!strcasecmp (pm
->pm_name
, "id")) {
1435 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1438 if (!strcasecmp (pm
->pm_name
, "number")) {
1439 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1440 || p
->pm_partno
< 1) {
1442 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1443 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1444 ct
->c_file
, TYPE_FIELD
);
1449 if (!strcasecmp (pm
->pm_name
, "total")) {
1450 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1459 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1460 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1461 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1467 case MESSAGE_EXTERNAL
:
1475 ct
->c_ctparams
= (void *) e
;
1478 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1479 advise (ct
->c_file
, "unable to open for reading");
1483 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1485 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1493 p
->c_ceopenfnx
= NULL
;
1494 if ((exresult
= params_external (ct
, 0)) != NOTOK
1495 && p
->c_ceopenfnx
== openMail
) {
1499 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1501 content_error (NULL
, ct
,
1502 "empty body for access-type=mail-server");
1506 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1507 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1509 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1511 adios ("failed", "fread");
1514 adios (NULL
, "unexpected EOF from fread");
1517 bp
+= cc
, size
-= cc
;
1524 p
->c_end
= p
->c_begin
;
1529 if (exresult
== NOTOK
)
1531 if (e
->eb_flags
== NOTOK
)
1534 switch (p
->c_type
) {
1539 if (p
->c_subtype
!= MESSAGE_RFC822
)
1543 e
->eb_partno
= ct
->c_partno
;
1545 (*p
->c_ctinitfnx
) (p
);
1560 params_external (CT ct
, int composing
)
1563 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1564 CI ci
= &ct
->c_ctinfo
;
1566 ct
->c_ceopenfnx
= NULL
;
1567 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1568 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1569 struct str2init
*s2i
;
1570 CT p
= e
->eb_content
;
1572 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1573 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1576 e
->eb_access
= pm
->pm_value
;
1577 e
->eb_flags
= NOTOK
;
1578 p
->c_encoding
= CE_EXTERNAL
;
1581 e
->eb_access
= s2i
->si_key
;
1582 e
->eb_flags
= s2i
->si_val
;
1583 p
->c_encoding
= CE_EXTERNAL
;
1585 /* Call the Init function for this external type */
1586 if ((*s2i
->si_init
)(p
) == NOTOK
)
1590 if (!strcasecmp (pm
->pm_name
, "name")) {
1591 e
->eb_name
= pm
->pm_value
;
1594 if (!strcasecmp (pm
->pm_name
, "permission")) {
1595 e
->eb_permission
= pm
->pm_value
;
1598 if (!strcasecmp (pm
->pm_name
, "site")) {
1599 e
->eb_site
= pm
->pm_value
;
1602 if (!strcasecmp (pm
->pm_name
, "directory")) {
1603 e
->eb_dir
= pm
->pm_value
;
1606 if (!strcasecmp (pm
->pm_name
, "mode")) {
1607 e
->eb_mode
= pm
->pm_value
;
1610 if (!strcasecmp (pm
->pm_name
, "size")) {
1611 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1614 if (!strcasecmp (pm
->pm_name
, "server")) {
1615 e
->eb_server
= pm
->pm_value
;
1618 if (!strcasecmp (pm
->pm_name
, "subject")) {
1619 e
->eb_subject
= pm
->pm_value
;
1622 if (!strcasecmp (pm
->pm_name
, "url")) {
1624 * According to RFC 2017, we have to remove all whitespace from
1628 char *u
, *p
= pm
->pm_value
;
1629 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1631 for (; *p
!= '\0'; p
++) {
1632 if (! isspace((unsigned char) *p
))
1639 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1640 e
->eb_body
= getcpy (pm
->pm_value
);
1645 if (!e
->eb_access
) {
1646 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1647 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1660 InitApplication (CT ct
)
1662 CI ci
= &ct
->c_ctinfo
;
1665 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1672 * TRANSFER ENCODINGS
1676 init_encoding (CT ct
, OpenCEFunc openfnx
)
1678 ct
->c_ceopenfnx
= openfnx
;
1679 ct
->c_ceclosefnx
= close_encoding
;
1680 ct
->c_cesizefnx
= size_encoding
;
1687 close_encoding (CT ct
)
1689 CE ce
= &ct
->c_cefile
;
1698 static unsigned long
1699 size_encoding (CT ct
)
1704 CE ce
= &ct
->c_cefile
;
1707 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1708 return (long) st
.st_size
;
1711 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1712 return (long) st
.st_size
;
1716 if (ct
->c_encoding
== CE_EXTERNAL
)
1717 return ct
->c_end
- ct
->c_begin
;
1720 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1721 return ct
->c_end
- ct
->c_begin
;
1723 if (fstat (fd
, &st
) != NOTOK
)
1724 size
= (long) st
.st_size
;
1728 (*ct
->c_ceclosefnx
) (ct
);
1740 return init_encoding (ct
, openBase64
);
1745 openBase64 (CT ct
, char **file
)
1748 int fd
, own_ct_fp
= 0;
1749 char *cp
, *buffer
= NULL
;
1750 /* sbeck -- handle suffixes */
1752 CE ce
= &ct
->c_cefile
;
1753 unsigned char *decoded
;
1755 unsigned char digest
[16];
1758 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1763 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1764 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1770 if (*file
== NULL
) {
1773 ce
->ce_file
= mh_xstrdup(*file
);
1777 /* sbeck@cise.ufl.edu -- handle suffixes */
1779 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1780 if (ce
->ce_unlink
) {
1781 /* Create temporary file with filename extension. */
1782 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1783 adios(NULL
, "unable to create temporary file in %s",
1787 ce
->ce_file
= add (cp
, ce
->ce_file
);
1789 } else if (*file
== NULL
) {
1791 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1792 adios(NULL
, "unable to create temporary file in %s",
1795 ce
->ce_file
= mh_xstrdup(tempfile
);
1798 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1799 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1803 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1804 adios (NULL
, "internal error(1)");
1806 buffer
= mh_xmalloc (len
+ 1);
1809 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1810 content_error (ct
->c_file
, ct
, "unable to open for reading");
1816 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1819 switch (cc
= read (fd
, cp
, len
)) {
1821 content_error (ct
->c_file
, ct
, "error reading from");
1825 content_error (NULL
, ct
, "premature eof");
1836 /* decodeBase64() requires null-terminated input. */
1839 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1840 ct
->c_digested
? digest
: NULL
) != OK
)
1845 unsigned char *decoded_p
= decoded
;
1846 for (i
= 0; i
< decoded_len
; ++i
) {
1847 putc (*decoded_p
++, ce
->ce_fp
);
1850 if (ferror (ce
->ce_fp
)) {
1851 content_error (ce
->ce_file
, ct
, "error writing to");
1855 if (ct
->c_digested
) {
1856 if (memcmp(digest
, ct
->c_digest
,
1858 content_error (NULL
, ct
,
1859 "content integrity suspect (digest mismatch) -- continuing");
1862 fprintf (stderr
, "content integrity confirmed\n");
1868 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1870 if (fflush (ce
->ce_fp
)) {
1871 content_error (ce
->ce_file
, ct
, "error writing to");
1875 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1878 *file
= ce
->ce_file
;
1884 return fileno (ce
->ce_fp
);
1891 free_encoding (ct
, 0);
1901 static char hex2nib
[0x80] = {
1902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1909 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1910 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 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, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1915 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1916 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1917 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1924 return init_encoding (ct
, openQuoted
);
1929 openQuoted (CT ct
, char **file
)
1931 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1937 CE ce
= &ct
->c_cefile
;
1938 /* sbeck -- handle suffixes */
1943 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1948 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1949 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1955 if (*file
== NULL
) {
1958 ce
->ce_file
= mh_xstrdup(*file
);
1962 /* sbeck@cise.ufl.edu -- handle suffixes */
1964 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1965 if (ce
->ce_unlink
) {
1966 /* Create temporary file with filename extension. */
1967 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1968 adios(NULL
, "unable to create temporary file in %s",
1972 ce
->ce_file
= add (cp
, ce
->ce_file
);
1974 } else if (*file
== NULL
) {
1976 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1977 adios(NULL
, "unable to create temporary file in %s",
1980 ce
->ce_file
= mh_xstrdup(tempfile
);
1983 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1984 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1988 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1989 adios (NULL
, "internal error(2)");
1992 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1993 content_error (ct
->c_file
, ct
, "unable to open for reading");
1999 if ((digested
= ct
->c_digested
))
2000 MD5Init (&mdContext
);
2004 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2006 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2007 content_error (NULL
, ct
, "premature eof");
2011 if ((cc
= gotlen
) > len
)
2015 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2016 if (!isspace ((unsigned char) *ep
))
2021 for (; cp
< ep
; cp
++) {
2023 /* in an escape sequence */
2025 /* at byte 1 of an escape sequence */
2026 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2027 /* next is byte 2 */
2030 /* at byte 2 of an escape sequence */
2032 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2033 putc (mask
, ce
->ce_fp
);
2035 MD5Update (&mdContext
, &mask
, 1);
2036 if (ferror (ce
->ce_fp
)) {
2037 content_error (ce
->ce_file
, ct
, "error writing to");
2040 /* finished escape sequence; next may be literal or a new
2041 * escape sequence */
2044 /* on to next byte */
2048 /* not in an escape sequence */
2050 /* starting an escape sequence, or invalid '='? */
2051 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2052 /* "=\n" soft line break, eat the \n */
2056 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2057 /* We don't have 2 bytes left, so this is an invalid
2058 * escape sequence; just show the raw bytes (below). */
2059 } else if (isxdigit ((unsigned char) cp
[1]) &&
2060 isxdigit ((unsigned char) cp
[2])) {
2061 /* Next 2 bytes are hex digits, making this a valid escape
2062 * sequence; let's decode it (above). */
2066 /* One or both of the next 2 is out of range, making this
2067 * an invalid escape sequence; just show the raw bytes
2071 /* Just show the raw byte. */
2072 putc (*cp
, ce
->ce_fp
);
2075 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2077 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2080 if (ferror (ce
->ce_fp
)) {
2081 content_error (ce
->ce_file
, ct
, "error writing to");
2087 content_error (NULL
, ct
,
2088 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2092 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2094 if (fflush (ce
->ce_fp
)) {
2095 content_error (ce
->ce_file
, ct
, "error writing to");
2100 unsigned char digest
[16];
2102 MD5Final (digest
, &mdContext
);
2103 if (memcmp(digest
, ct
->c_digest
,
2105 content_error (NULL
, ct
,
2106 "content integrity suspect (digest mismatch) -- continuing");
2108 fprintf (stderr
, "content integrity confirmed\n");
2111 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2114 *file
= ce
->ce_file
;
2120 return fileno (ce
->ce_fp
);
2123 free_encoding (ct
, 0);
2140 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2143 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2149 open7Bit (CT ct
, char **file
)
2151 int cc
, fd
, len
, own_ct_fp
= 0;
2152 char buffer
[BUFSIZ
];
2153 /* sbeck -- handle suffixes */
2156 CE ce
= &ct
->c_cefile
;
2159 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2164 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2165 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2171 if (*file
== NULL
) {
2174 ce
->ce_file
= mh_xstrdup(*file
);
2178 /* sbeck@cise.ufl.edu -- handle suffixes */
2180 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2181 if (ce
->ce_unlink
) {
2182 /* Create temporary file with filename extension. */
2183 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2184 adios(NULL
, "unable to create temporary file in %s",
2188 ce
->ce_file
= add (cp
, ce
->ce_file
);
2190 } else if (*file
== NULL
) {
2192 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2193 adios(NULL
, "unable to create temporary file in %s",
2196 ce
->ce_file
= mh_xstrdup(tempfile
);
2199 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2200 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2204 if (ct
->c_type
== CT_MULTIPART
) {
2205 CI ci
= &ct
->c_ctinfo
;
2209 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2210 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2211 + 1 + strlen (ci
->ci_subtype
);
2212 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2215 fputs (buffer
, ce
->ce_fp
);
2219 if (ci
->ci_comment
) {
2220 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2221 fputs ("\n\t", ce
->ce_fp
);
2225 putc (' ', ce
->ce_fp
);
2228 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2231 fprintf (ce
->ce_fp
, "\n");
2233 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2235 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2237 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2238 fprintf (ce
->ce_fp
, "\n");
2241 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2242 adios (NULL
, "internal error(3)");
2245 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2246 content_error (ct
->c_file
, ct
, "unable to open for reading");
2252 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2254 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2256 content_error (ct
->c_file
, ct
, "error reading from");
2260 content_error (NULL
, ct
, "premature eof");
2268 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2269 advise ("open7Bit", "fwrite");
2271 if (ferror (ce
->ce_fp
)) {
2272 content_error (ce
->ce_file
, ct
, "error writing to");
2277 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2279 if (fflush (ce
->ce_fp
)) {
2280 content_error (ce
->ce_file
, ct
, "error writing to");
2284 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2287 *file
= ce
->ce_file
;
2292 return fileno (ce
->ce_fp
);
2295 free_encoding (ct
, 0);
2309 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2311 char cachefile
[BUFSIZ
];
2314 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2319 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2320 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2326 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2327 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2328 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2329 ce
->ce_file
= mh_xstrdup(cachefile
);
2333 admonish (cachefile
, "unable to fopen for reading");
2336 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2340 *file
= ce
->ce_file
;
2341 *fd
= fileno (ce
->ce_fp
);
2352 return init_encoding (ct
, openFile
);
2357 openFile (CT ct
, char **file
)
2360 char cachefile
[BUFSIZ
];
2361 struct exbody
*e
= ct
->c_ctexbody
;
2362 CE ce
= &ct
->c_cefile
;
2364 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2376 content_error (NULL
, ct
, "missing name parameter");
2380 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2383 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2384 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2388 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2389 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2390 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2394 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2395 if ((fp
= fopen (cachefile
, "w"))) {
2397 char buffer
[BUFSIZ
];
2398 FILE *gp
= ce
->ce_fp
;
2400 fseek (gp
, 0L, SEEK_SET
);
2402 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2404 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2405 advise ("openFile", "fwrite");
2410 admonish (ce
->ce_file
, "error reading");
2411 (void) m_unlink (cachefile
);
2412 } else if (ferror (fp
)) {
2413 admonish (cachefile
, "error writing");
2414 (void) m_unlink (cachefile
);
2421 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2422 *file
= ce
->ce_file
;
2423 return fileno (ce
->ce_fp
);
2433 return init_encoding (ct
, openFTP
);
2438 openFTP (CT ct
, char **file
)
2440 int cachetype
, caching
, fd
;
2442 char *bp
, *ftp
, *user
, *pass
;
2443 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2445 CE ce
= &ct
->c_cefile
;
2446 static char *username
= NULL
;
2447 static char *password
= NULL
;
2451 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2457 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2468 if (!e
->eb_name
|| !e
->eb_site
) {
2469 content_error (NULL
, ct
, "missing %s parameter",
2470 e
->eb_name
? "site": "name");
2474 /* Get the buffer ready to go */
2476 buflen
= sizeof(buffer
);
2479 * Construct the query message for user
2481 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2487 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2493 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2494 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2499 if (e
->eb_size
> 0) {
2500 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2505 snprintf (bp
, buflen
, "? ");
2508 * Now, check the answer
2510 if (!read_yes_or_no_if_tty (buffer
))
2515 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2519 ruserpass (e
->eb_site
, &username
, &password
, 0);
2524 ce
->ce_unlink
= (*file
== NULL
);
2526 cachefile
[0] = '\0';
2527 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2528 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2529 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2530 if (*file
== NULL
) {
2537 ce
->ce_file
= mh_xstrdup(*file
);
2539 ce
->ce_file
= mh_xstrdup(cachefile
);
2542 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2543 adios(NULL
, "unable to create temporary file in %s",
2546 ce
->ce_file
= mh_xstrdup(tempfile
);
2549 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2550 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2559 vec
[vecp
++] = r1bindex (ftp
, '/');
2560 vec
[vecp
++] = e
->eb_site
;
2563 vec
[vecp
++] = e
->eb_dir
;
2564 vec
[vecp
++] = e
->eb_name
;
2565 vec
[vecp
++] = ce
->ce_file
,
2566 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2567 ? "ascii" : "binary";
2575 adios ("fork", "unable to");
2579 close (fileno (ce
->ce_fp
));
2581 fprintf (stderr
, "unable to exec ");
2587 if (pidXwait (child_id
, NULL
)) {
2588 username
= password
= NULL
;
2598 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2603 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2604 if ((fp
= fopen (cachefile
, "w"))) {
2606 FILE *gp
= ce
->ce_fp
;
2608 fseek (gp
, 0L, SEEK_SET
);
2610 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2612 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2613 advise ("openFTP", "fwrite");
2618 admonish (ce
->ce_file
, "error reading");
2619 (void) m_unlink (cachefile
);
2620 } else if (ferror (fp
)) {
2621 admonish (cachefile
, "error writing");
2622 (void) m_unlink (cachefile
);
2630 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2631 *file
= ce
->ce_file
;
2632 return fileno (ce
->ce_fp
);
2643 return init_encoding (ct
, openMail
);
2648 openMail (CT ct
, char **file
)
2650 int child_id
, fd
, vecp
;
2652 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2653 struct exbody
*e
= ct
->c_ctexbody
;
2654 CE ce
= &ct
->c_cefile
;
2656 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2667 if (!e
->eb_server
) {
2668 content_error (NULL
, ct
, "missing server parameter");
2672 /* Get buffer ready to go */
2674 buflen
= sizeof(buffer
);
2676 /* Now, construct query message */
2677 snprintf (bp
, buflen
, "Retrieve content");
2683 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2689 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2691 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2693 /* Now, check answer */
2694 if (!read_yes_or_no_if_tty (buffer
))
2698 vec
[vecp
++] = r1bindex (mailproc
, '/');
2699 vec
[vecp
++] = e
->eb_server
;
2700 vec
[vecp
++] = "-subject";
2701 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2702 vec
[vecp
++] = "-body";
2703 vec
[vecp
++] = e
->eb_body
;
2709 advise ("fork", "unable to");
2713 execvp (mailproc
, vec
);
2714 fprintf (stderr
, "unable to exec ");
2720 if (pidXwait (child_id
, NULL
) == OK
)
2721 inform("request sent");
2725 if (*file
== NULL
) {
2727 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2728 adios(NULL
, "unable to create temporary file in %s",
2731 ce
->ce_file
= mh_xstrdup(tempfile
);
2734 ce
->ce_file
= mh_xstrdup(*file
);
2738 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2739 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2743 /* showproc is for mhshow and mhstore, though mhlist -debug
2744 * prints it, too. */
2745 free(ct
->c_showproc
);
2746 ct
->c_showproc
= mh_xstrdup("true");
2748 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2749 *file
= ce
->ce_file
;
2750 return fileno (ce
->ce_fp
);
2761 return init_encoding (ct
, openURL
);
2766 openURL (CT ct
, char **file
)
2768 struct exbody
*e
= ct
->c_ctexbody
;
2769 CE ce
= &ct
->c_cefile
;
2770 char *urlprog
, *program
;
2771 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2772 int fd
, caching
, cachetype
;
2773 struct msgs_array args
= { 0, 0, NULL
};
2776 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2780 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2784 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2796 content_error(NULL
, ct
, "missing url parameter");
2800 ce
->ce_unlink
= (*file
== NULL
);
2802 cachefile
[0] = '\0';
2804 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2805 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2806 if (*file
== NULL
) {
2813 ce
->ce_file
= mh_xstrdup(*file
);
2815 ce
->ce_file
= mh_xstrdup(cachefile
);
2818 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2819 adios(NULL
, "unable to create temporary file in %s",
2822 ce
->ce_file
= mh_xstrdup(tempfile
);
2825 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2826 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2830 switch (child_id
= fork()) {
2832 adios ("fork", "unable to");
2836 argsplit_msgarg(&args
, urlprog
, &program
);
2837 app_msgarg(&args
, e
->eb_url
);
2838 app_msgarg(&args
, NULL
);
2839 dup2(fileno(ce
->ce_fp
), 1);
2840 close(fileno(ce
->ce_fp
));
2841 execvp(program
, args
.msgs
);
2842 fprintf(stderr
, "Unable to exec ");
2848 if (pidXwait(child_id
, NULL
)) {
2856 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2861 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2862 if ((fp
= fopen(cachefile
, "w"))) {
2864 FILE *gp
= ce
->ce_fp
;
2866 fseeko(gp
, 0, SEEK_SET
);
2868 while ((cc
= fread(buffer
, sizeof(*buffer
),
2869 sizeof(buffer
), gp
)) > 0)
2870 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2871 advise ("openURL", "fwrite");
2877 admonish(ce
->ce_file
, "error reading");
2878 (void) m_unlink (cachefile
);
2885 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2886 *file
= ce
->ce_file
;
2887 return fileno(ce
->ce_fp
);
2892 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2893 * has to be base64 decoded.
2896 readDigest (CT ct
, char *cp
)
2898 unsigned char *digest
;
2901 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2902 const size_t maxlen
= sizeof ct
->c_digest
;
2904 if (strlen ((char *) digest
) <= maxlen
) {
2905 memcpy (ct
->c_digest
, digest
, maxlen
);
2910 fprintf (stderr
, "MD5 digest=");
2911 for (i
= 0; i
< maxlen
; ++i
) {
2912 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2914 fprintf (stderr
, "\n");
2920 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2921 (int) strlen ((char *) digest
));
2931 /* Multipart parts might have content before the first subpart and/or
2932 after the last subpart that hasn't been stored anywhere else, so do
2935 get_leftover_mp_content (CT ct
, int before
/* or after */)
2937 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2939 int found_boundary
= 0;
2945 char *content
= NULL
;
2947 if (! m
) return NOTOK
;
2950 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2952 /* Isolate the beginning of this part to the beginning of the
2953 first subpart and save any content between them. */
2954 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2955 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2956 boundary
= concat ("--", m
->mp_start
, NULL
);
2958 struct part
*last_subpart
= NULL
;
2959 struct part
*subpart
;
2961 /* Go to the last subpart to get its end position. */
2962 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2963 last_subpart
= subpart
;
2966 if (last_subpart
== NULL
) return NOTOK
;
2968 /* Isolate the end of the last subpart to the end of this part
2969 and save any content between them. */
2970 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2971 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2972 boundary
= concat ("--", m
->mp_stop
, NULL
);
2975 /* Back up by 1 to pick up the newline. */
2976 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2978 /* Don't look beyond beginning of first subpart (before) or
2979 next part (after). */
2980 if (read
> max
) bufp
[read
-max
] = '\0';
2983 if (! strcmp (bufp
, boundary
)) {
2987 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2993 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2995 char *old_content
= content
;
2996 content
= concat (content
, bufp
, NULL
);
3000 ? concat ("\n", bufp
, NULL
)
3001 : concat (bufp
, NULL
);
3006 if (found_boundary
|| read
> max
) break;
3008 if (read
> max
) break;
3012 /* Skip the newline if that's all there is. */
3016 /* Remove trailing newline, except at EOF. */
3017 if ((before
|| ! feof (ct
->c_fp
)) &&
3018 (cp
= content
+ strlen (content
)) > content
&&
3023 if (strlen (content
) > 1) {
3025 m
->mp_content_before
= content
;
3027 m
->mp_content_after
= content
;
3042 ct_type_str (int type
) {
3044 case CT_APPLICATION
:
3045 return "application";
3061 return "unknown_type";
3067 ct_subtype_str (int type
, int subtype
) {
3069 case CT_APPLICATION
:
3071 case APPLICATION_OCTETS
:
3073 case APPLICATION_POSTSCRIPT
:
3074 return "postscript";
3076 return "unknown_app_subtype";
3080 case MESSAGE_RFC822
:
3082 case MESSAGE_PARTIAL
:
3084 case MESSAGE_EXTERNAL
:
3087 return "unknown_msg_subtype";
3093 case MULTI_ALTERNATE
:
3094 return "alternative";
3097 case MULTI_PARALLEL
:
3102 return "unknown_multipart_subtype";
3113 return "unknown_text_subtype";
3116 return "unknown_type";
3122 ct_str_type (const char *type
) {
3123 struct str2init
*s2i
;
3125 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3126 if (! strcasecmp (type
, s2i
->si_key
)) {
3130 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3139 ct_str_subtype (int type
, const char *subtype
) {
3143 case CT_APPLICATION
:
3144 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3145 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3149 return kv
->kv_value
;
3151 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3152 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3156 return kv
->kv_value
;
3158 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3159 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3163 return kv
->kv_value
;
3165 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3166 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3170 return kv
->kv_value
;
3177 /* Find the content type and InitFunc for the CT. */
3178 const struct str2init
*
3179 get_ct_init (int type
) {
3180 const struct str2init
*sp
;
3182 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3183 if (type
== sp
->si_val
) {
3192 ce_str (int encoding
) {
3197 return "quoted-printable";
3213 /* Find the content type and InitFunc for the content encoding method. */
3214 const struct str2init
*
3215 get_ce_method (const char *method
) {
3216 struct str2init
*sp
;
3218 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3219 if (! strcasecmp (method
, sp
->si_key
)) {
3228 * Parse a series of MIME attributes (or parameters) given a header as
3231 * Arguments include:
3233 * filename - Name of input file (for error messages)
3234 * fieldname - Name of field being processed
3235 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3236 * Updated to point to end of attributes when finished.
3237 * param_head - Pointer to head of parameter list
3238 * param_tail - Pointer to tail of parameter list
3239 * commentp - Pointer to header comment pointer (may be NULL)
3241 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3242 * DONE to indicate a benign error (minor parsing error, but the program
3247 parse_header_attrs (const char *filename
, const char *fieldname
,
3248 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3251 char *cp
= *header_attrp
;
3257 struct sectlist
*next
;
3263 struct sectlist
*sechead
;
3264 struct parmlist
*next
;
3265 } *pp
, *pp2
, *phead
= NULL
;
3267 while (*cp
== ';') {
3268 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3269 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3272 while (isspace ((unsigned char) *cp
))
3276 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3281 if (! suppress_extraneous_trailing_semicolon_warning
) {
3282 inform("extraneous trailing ';' in message %s's %s: "
3283 "parameter list", filename
, fieldname
);
3288 /* down case the attribute name */
3289 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3290 *dp
= tolower ((unsigned char) *dp
);
3292 for (up
= dp
; isspace ((unsigned char) *dp
);)
3294 if (dp
== cp
|| *dp
!= '=') {
3295 inform("invalid parameter in message %s's %s: field\n"
3296 " parameter %s (error detected at offset %ld)",
3297 filename
, fieldname
, cp
, (long)(dp
- cp
));
3302 * To handle RFC 2231, we have to deal with the following extensions:
3304 * name*=encoded-value
3305 * name*<N>=part-N-of-a-parameter-value
3306 * name*<N>*=encoded-part-N-of-a-parameter-value
3309 * If there's a * right before the equal sign, it's encoded.
3310 * If there's a * and one or more digits, then it's section N.
3312 * Remember we can have one or the other, or both. cp points to
3313 * beginning of name, up points past the last character in the
3317 for (vp
= cp
; vp
< up
; vp
++) {
3318 if (*vp
== '*' && vp
< up
- 1) {
3322 if (*vp
== '*' && vp
== up
- 1) {
3324 } else if (partial
) {
3325 if (isdigit((unsigned char) *vp
))
3326 index
= *vp
- '0' + index
* 10;
3328 inform("invalid parameter index in message %s's %s: field"
3329 "\n (parameter %s)", filename
, fieldname
, cp
);
3338 * Break out the parameter name and value sections and allocate
3342 nameptr
= mh_xmalloc(len
+ 1);
3343 strncpy(nameptr
, cp
, len
);
3344 nameptr
[len
] = '\0';
3346 for (dp
++; isspace ((unsigned char) *dp
);)
3351 * Single quotes delimit the character set and language tag.
3352 * They are required on the first section (or a complete
3357 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3363 charset
= mh_xmalloc(len
+ 1);
3364 strncpy(charset
, dp
, len
);
3365 charset
[len
] = '\0';
3371 inform("missing charset in message %s's %s: field\n"
3372 " (parameter %s)", filename
, fieldname
, nameptr
);
3378 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3385 lang
= mh_xmalloc(len
+ 1);
3386 strncpy(lang
, dp
, len
);
3393 inform("missing language tag in message %s's %s: field\n"
3394 " (parameter %s)", filename
, fieldname
, nameptr
);
3404 * At this point vp should be pointing at the beginning
3405 * of the encoded value/section. Continue until we reach
3406 * the end or get whitespace. But first, calculate the
3407 * length so we can allocate the correct buffer size.
3410 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3412 if (*(vp
+ 1) == '\0' ||
3413 !isxdigit((unsigned char) *(vp
+ 1)) ||
3414 *(vp
+ 2) == '\0' ||
3415 !isxdigit((unsigned char) *(vp
+ 2))) {
3416 inform("invalid encoded sequence in message %s's %s: field\n"
3417 " (parameter %s)", filename
, fieldname
, nameptr
);
3428 up
= valptr
= mh_xmalloc(len
+ 1);
3430 for (vp
= dp
; istoken(*vp
); vp
++) {
3432 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3443 * A "normal" string. If it's got a leading quote, then we
3444 * strip the quotes out. Otherwise go until we reach the end
3445 * or get whitespace. Note we scan it twice; once to get the
3446 * length, then the second time copies it into the destination
3453 for (cp
= dp
+ 1;;) {
3457 inform("invalid quoted-string in message %s's %s: field\n"
3458 " (parameter %s)", filename
, fieldname
, nameptr
);
3478 for (cp
= dp
; istoken (*cp
); cp
++) {
3483 valptr
= mh_xmalloc(len
+ 1);
3487 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3495 strncpy(valptr
, cp
= dp
, len
);
3503 * If 'partial' is set, we don't allocate a parameter now. We
3504 * put it on the parameter linked list to be reassembled later.
3506 * "phead" points to a list of all parameters we need to reassemble.
3507 * Each parameter has a list of sections. We insert the sections in
3512 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3513 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3528 * Insert this into the section linked list
3536 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3537 sp
->next
= pp
->sechead
;
3540 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3541 if (sp2
->index
== sp
->index
) {
3542 inform("duplicate index (%d) in message %s's %s: field"
3543 "\n (parameter %s)", sp
->index
, filename
,
3544 fieldname
, nameptr
);
3547 if (sp2
->index
< sp
->index
&&
3548 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3549 sp
->next
= sp2
->next
;
3556 inform("Internal error: cannot insert partial param "
3557 "in message %s's %s: field\n (parameter %s)",
3558 filename
, fieldname
, nameptr
);
3564 * Save our charset and lang tags.
3567 if (index
== 0 && encoded
) {
3569 pp
->charset
= charset
;
3574 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3575 pm
->pm_charset
= charset
;
3579 while (isspace ((unsigned char) *cp
))
3583 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3589 * Now that we're done, reassemble all of the partial parameters.
3592 for (pp
= phead
; pp
!= NULL
; ) {
3596 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3597 if (sp
->index
!= pindex
++) {
3598 inform("missing section %d for parameter in message "
3599 "%s's %s: field\n (parameter %s)", pindex
- 1,
3600 filename
, fieldname
, pp
->name
);
3606 p
= q
= mh_xmalloc(tlen
+ 1);
3607 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3608 memcpy(q
, sp
->value
, sp
->len
);
3618 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3619 pm
->pm_charset
= pp
->charset
;
3620 pm
->pm_lang
= pp
->lang
;
3631 * Return the charset for a particular content type.
3635 content_charset (CT ct
) {
3636 char *ret_charset
= NULL
;
3638 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3640 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3645 * Create a string based on a list of output parameters. Assume that this
3646 * parameter string will be appended to an existing header, so start out
3647 * with the separator (;). Perform RFC 2231 encoding when necessary.
3651 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3653 char *paramout
= NULL
;
3654 char line
[CPERLIN
* 2], *q
;
3655 int curlen
, index
, cont
, encode
, i
;
3656 size_t valoff
, numchars
;
3658 while (params
!= NULL
) {
3664 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3667 if (strlen(params
->pm_name
) > CPERLIN
) {
3668 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3673 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3676 * Loop until we get a parameter that fits within a line. We
3677 * assume new lines start with a tab, so check our overflow based
3687 * At this point we're definitely continuing the line, so
3688 * be sure to include the parameter name and section index.
3691 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3692 params
->pm_name
, index
);
3695 * Both of these functions do a NUL termination
3699 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3700 numchars
, valoff
, index
);
3702 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3712 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3717 * "line" starts with a ;\n\t, so that doesn't count against
3718 * the length. But add 8 since it starts with a tab; that's
3719 * how we end up with 5.
3722 initialwidth
= strlen(line
) + 5;
3725 * At this point the line should be built, so add it to our
3726 * current output buffer.
3729 paramout
= add(line
, paramout
);
3733 * If this won't fit on the line, start a new one. Save room in
3734 * case we need a semicolon on the end
3737 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3749 * At this point, we're either finishing a continued parameter, or
3750 * we're working on a new one.
3754 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3755 params
->pm_name
, index
);
3757 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3762 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3763 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3765 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3766 strlen(params
->pm_value
+ valoff
), valoff
);
3773 paramout
= add(line
, paramout
);
3774 initialwidth
+= strlen(line
);
3776 params
= params
->pm_next
;
3780 *offsetout
= initialwidth
;
3786 * Calculate the size of a parameter.
3790 * pm - The parameter being output
3791 * index - If continuing the parameter, the index of the section
3793 * valueoff - The current offset into the parameter value that we're
3794 * working on (previous sections have consumed valueoff bytes).
3795 * encode - Set if we should perform encoding on this parameter section
3796 * (given that we're consuming bytesfit bytes).
3797 * cont - Set if the remaining data in value will not fit on a single
3798 * line and will need to be continued.
3799 * bytesfit - The number of bytes that we can consume from the parameter
3800 * value and still fit on a completely new line. The
3801 * calculation assumes the new line starts with a tab,
3802 * includes the parameter name and any encoding, and fits
3803 * within CPERLIN bytes. Will always be at least 1.
3807 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3810 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3811 size_t len
= 0, fit
= 0;
3812 int fitlimit
= 0, eightbit
, maxfit
;
3817 * Add up the length. First, start with the parameter name.
3820 len
= strlen(pm
->pm_name
);
3823 * Scan the parameter value and see if we need to do encoding for this
3827 eightbit
= contains8bit(start
, NULL
);
3830 * Determine if we need to encode this section. Encoding is necessary if:
3832 * - There are any 8-bit characters at all and we're on the first
3834 * - There are 8-bit characters within N bytes of our section start.
3835 * N is calculated based on the number of bytes it would take to
3836 * reach CPERLIN. Specifically:
3837 * 8 (starting tab) +
3838 * strlen(param name) +
3839 * 4 ('* for section marker, '=', opening/closing '"')
3841 * is the number of bytes used by everything that isn't part of the
3842 * value. So that gets subtracted from CPERLIN.
3845 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3846 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3847 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3851 len
++; /* Add in equal sign */
3855 * We're using maxfit as a marker for how many characters we can
3856 * fit into the line. Bump it by two because we're not using quotes
3863 * If we don't have a charset or language tag in this parameter,
3867 if (! pm
->pm_charset
) {
3868 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3869 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3870 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3871 "local character set is US-ASCII", pm
->pm_name
);
3874 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3876 len
++; /* For the encoding marker */
3879 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3884 * We know we definitely need to include an index. maxfit already
3885 * includes the section marker.
3887 len
+= strlen(indexchar
);
3889 for (p
= start
; *p
!= '\0'; p
++) {
3890 if (isparamencode(*p
)) {
3898 * Just so there's no confusion: maxfit is counting OUTPUT
3899 * characters (post-encoding). fit is counting INPUT characters.
3901 if (! fitlimit
&& maxfit
>= 0)
3903 else if (! fitlimit
)
3908 * Calculate the string length, but add room for quoting \
3909 * and " if necessary. Also account for quotes at beginning
3912 for (p
= start
; *p
!= '\0'; p
++) {
3923 if (! fitlimit
&& maxfit
>= 0)
3925 else if (! fitlimit
)
3942 * Output an encoded parameter string.
3946 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3947 size_t valueoff
, int index
)
3949 size_t outlen
= 0, n
;
3950 char *endptr
= output
+ len
, *p
;
3953 * First, output the marker for an encoded string.
3961 * If the index is 0, output the character set and language tag.
3962 * If theses were NULL, they should have already been filled in
3967 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3971 if (output
> endptr
) {
3972 inform("Internal error: parameter buffer overflow");
3978 * Copy over the value, encoding if necessary
3981 p
= pm
->pm_value
+ valueoff
;
3982 while (valuelen
-- > 0) {
3983 if (isparamencode(*p
)) {
3984 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
3991 if (output
> endptr
) {
3992 inform("Internal error: parameter buffer overflow");
4003 * Output a "normal" parameter, without encoding. Be sure to escape
4004 * quotes and backslashes if necessary.
4008 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4012 char *endptr
= output
+ len
, *p
;
4018 p
= pm
->pm_value
+ valueoff
;
4020 while (valuelen
-- > 0) {
4031 if (output
> endptr
) {
4032 inform("Internal error: parameter buffer overflow");
4037 if (output
- 2 > endptr
) {
4038 inform("Internal error: parameter buffer overflow");
4049 * Add a parameter to the parameter linked list
4053 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4058 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4059 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4062 (*last
)->pm_next
= pm
;
4073 * Either replace a current parameter with a new value, or add the parameter
4074 * to the parameter linked list.
4078 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4082 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4083 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4085 * If nocopy is set, it's assumed that we own both name
4086 * and value. We don't need name, so we discard it now.
4091 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4096 return add_param(first
, last
, name
, value
, nocopy
);
4100 * Retrieve a parameter value from a parameter linked list. If the parameter
4101 * value needs converted to the local character set, do that now.
4105 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4107 while (first
!= NULL
) {
4108 if (strcasecmp(name
, first
->pm_name
) == 0) {
4110 return first
->pm_value
;
4111 return getcpy(get_param_value(first
, replace
));
4113 first
= first
->pm_next
;
4120 * Return a parameter value, converting to the local character set if
4124 char *get_param_value(PM pm
, char replace
)
4126 static char buffer
[4096]; /* I hope no parameters are larger */
4127 size_t bufsize
= sizeof(buffer
);
4132 ICONV_CONST
char *p
;
4133 #else /* HAVE_ICONV */
4135 #endif /* HAVE_ICONV */
4140 * If we don't have a character set indicated, it's assumed to be
4141 * US-ASCII. If it matches our character set, we don't need to convert
4145 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4146 strlen(pm
->pm_charset
))) {
4147 return pm
->pm_value
;
4151 * In this case, we need to convert. If we have iconv support, use
4152 * that. Otherwise, go through and simply replace every non-ASCII
4153 * character with the substitution character.
4158 bufsize
= sizeof(buffer
);
4159 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4161 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4162 if (cd
== (iconv_t
) -1) {
4166 inbytes
= strlen(pm
->pm_value
);
4170 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4171 if (errno
!= EILSEQ
) {
4176 * Reset shift state, substitute our character,
4177 * try to restart conversion.
4180 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4193 for (++p
, --inbytes
;
4194 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4213 #endif /* HAVE_ICONV */
4216 * Take everything non-ASCII and substitute the replacement character
4220 bufsize
= sizeof(buffer
);
4221 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4222 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))