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/check_charset.h"
10 #include "sbr/getcpy.h"
11 #include "sbr/context_find.h"
12 #include "sbr/pidstatus.h"
13 #include "sbr/arglist.h"
14 #include "sbr/error.h"
20 #include "h/mhparse.h"
23 #include "h/mhcachesbr.h"
24 #include "sbr/m_mktemp.h"
28 #endif /* HAVE_ICONV */
29 #include "sbr/base64.h"
34 int checksw
= 0; /* check Content-MD5 field */
37 * These are for mhfixmsg to:
38 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
40 * 2) Suppress the warning about bogus multipart content, and report it.
41 * 3) Suppress the warning about extraneous trailing ';' in header parameter
44 bool skip_mp_cte_check
;
45 bool suppress_bogus_mp_content_warning
;
46 bool bogus_mp_content
;
47 bool suppress_extraneous_trailing_semicolon_warning
;
50 * By default, suppress warning about multiple MIME-Version header fields.
52 bool suppress_multiple_mime_version_warning
= true;
54 /* list of preferred type/subtype pairs, for -prefer */
55 mime_type_subtype mime_preference
[NPREFS
];
60 * Structures for TEXT messages
62 struct k2v SubText
[] = {
63 { "plain", TEXT_PLAIN
},
64 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
65 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
66 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
69 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
72 * Structures for MULTIPART messages
74 struct k2v SubMultiPart
[] = {
75 { "mixed", MULTI_MIXED
},
76 { "alternative", MULTI_ALTERNATE
},
77 { "digest", MULTI_DIGEST
},
78 { "parallel", MULTI_PARALLEL
},
79 { "related", MULTI_RELATED
},
80 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
84 * Structures for MESSAGE messages
86 struct k2v SubMessage
[] = {
87 { "rfc822", MESSAGE_RFC822
},
88 { "partial", MESSAGE_PARTIAL
},
89 { "external-body", MESSAGE_EXTERNAL
},
90 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
94 * Structure for APPLICATION messages
96 struct k2v SubApplication
[] = {
97 { "octet-stream", APPLICATION_OCTETS
},
98 { "postscript", APPLICATION_POSTSCRIPT
},
99 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
103 * Mapping of names of CTE types in mhbuild directives
105 static struct k2v EncodingType
[] = {
108 { "q-p", CE_QUOTED
},
109 { "quoted-printable", CE_QUOTED
},
110 { "b64", CE_BASE64
},
111 { "base64", CE_BASE64
},
119 static CT
get_content (FILE *, char *, int);
120 static int get_comment (const char *, const char *, char **, char **);
122 static int InitGeneric (CT
);
123 static int InitText (CT
);
124 static int InitMultiPart (CT
);
125 static void reverse_parts (CT
);
126 static void prefer_parts(CT ct
);
127 static int InitMessage (CT
);
128 static int InitApplication (CT
);
129 static int init_encoding (CT
, OpenCEFunc
);
130 static unsigned long size_encoding (CT
);
131 static int InitBase64 (CT
);
132 static int openBase64 (CT
, char **);
133 static int InitQuoted (CT
);
134 static int openQuoted (CT
, char **);
135 static int Init7Bit (CT
);
136 static int openExternal (CT
, CT
, CE
, char **, int *);
137 static int InitFile (CT
);
138 static int openFile (CT
, char **);
139 static int InitFTP (CT
);
140 static int openFTP (CT
, char **);
141 static int InitMail (CT
);
142 static int openMail (CT
, char **);
143 static int readDigest (CT
, char *);
144 static int get_leftover_mp_content (CT
, int);
145 static int InitURL (CT
);
146 static int openURL (CT
, char **);
147 static int parse_header_attrs (const char *, const char *, char **, PM
*,
149 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
150 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
151 static int get_dispo (char *, CT
, int);
153 struct str2init str2cts
[] = {
154 { "application", CT_APPLICATION
, InitApplication
},
155 { "audio", CT_AUDIO
, InitGeneric
},
156 { "image", CT_IMAGE
, InitGeneric
},
157 { "message", CT_MESSAGE
, InitMessage
},
158 { "multipart", CT_MULTIPART
, InitMultiPart
},
159 { "text", CT_TEXT
, InitText
},
160 { "video", CT_VIDEO
, InitGeneric
},
161 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
162 { NULL
, CT_UNKNOWN
, NULL
},
165 struct str2init str2ces
[] = {
166 { "base64", CE_BASE64
, InitBase64
},
167 { "quoted-printable", CE_QUOTED
, InitQuoted
},
168 { "8bit", CE_8BIT
, Init7Bit
},
169 { "7bit", CE_7BIT
, Init7Bit
},
170 { "binary", CE_BINARY
, Init7Bit
},
171 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
172 { NULL
, CE_UNKNOWN
, NULL
},
176 * NOTE WELL: si_key MUST NOT have value of NOTOK
178 * si_val is 1 if access method is anonymous.
180 struct str2init str2methods
[] = {
181 { "afs", 1, InitFile
},
182 { "anon-ftp", 1, InitFTP
},
183 { "ftp", 0, InitFTP
},
184 { "local-file", 0, InitFile
},
185 { "mail-server", 0, InitMail
},
186 { "url", 0, InitURL
},
192 * Main entry point for parsing a MIME message or file.
193 * It returns the Content structure for the top level
194 * entity in the file.
198 parse_mime (char *file
)
207 bogus_mp_content
= false;
210 * Check if file is actually standard input
212 if ((is_stdin
= !(strcmp (file
, "-")))) {
213 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
215 advise("mhparse", "unable to create temporary file in %s",
219 file
= mh_xstrdup(tfile
);
221 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
222 if (fwrite(buffer
, 1, n
, fp
) != n
) {
223 (void) m_unlink (file
);
224 advise (file
, "error copying to temporary file");
230 if (ferror (stdin
)) {
231 (void) m_unlink (file
);
232 advise ("stdin", "error reading");
236 (void) m_unlink (file
);
237 advise (file
, "error writing");
240 fseek (fp
, 0L, SEEK_SET
);
241 } else if (stat (file
, &statbuf
) == NOTOK
) {
242 advise (file
, "unable to stat");
244 } else if (S_ISDIR(statbuf
.st_mode
)) {
245 /* Don't try to parse a directory. */
246 inform("%s is a directory", file
);
248 } else if ((fp
= fopen (file
, "r")) == NULL
) {
249 advise (file
, "unable to read");
253 if (!(ct
= get_content (fp
, file
, 1))) {
255 (void) m_unlink (file
);
256 inform("unable to decode %s", file
);
261 ct
->c_unlink
= 1; /* temp file to remove */
265 if (ct
->c_end
== 0L) {
266 fseek (fp
, 0L, SEEK_END
);
267 ct
->c_end
= ftell (fp
);
270 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
282 * Main routine for reading/parsing the headers
283 * of a message content.
285 * toplevel = 1 # we are at the top level of the message
286 * toplevel = 0 # we are inside message type or multipart type
287 * # other than multipart/digest
288 * toplevel = -1 # we are inside multipart/digest
289 * NB: on failure we will fclose(in)!
293 get_content (FILE *in
, char *file
, int toplevel
)
296 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
300 m_getfld_state_t gstate
;
302 /* allocate the content structure */
305 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
306 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
309 * Parse the header fields for this
310 * content into a linked list.
312 gstate
= m_getfld_state_init(in
);
313 m_getfld_track_filepos2(&gstate
);
314 for (compnum
= 1;;) {
315 int bufsz
= sizeof buf
;
316 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
321 /* get copies of the buffers */
322 np
= mh_xstrdup(name
);
323 vp
= mh_xstrdup(buf
);
325 /* if necessary, get rest of field */
326 while (state
== FLDPLUS
) {
328 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
329 vp
= add (buf
, vp
); /* add to previous value */
332 /* Now add the header data to the list */
333 add_header (ct
, np
, vp
);
335 /* continue, to see if this isn't the last header field */
336 ct
->c_begin
= ftell (in
) + 1;
340 /* There are two cases. The unusual one is when there is no
341 * blank line between the headers and the body. This is
342 * indicated by the name of the header starting with `:'.
344 * For both cases, normal first, `1' is the desired c_begin
345 * file position for the start of the body, and `2' is the
346 * file position when buf is returned.
348 * f o o : b a r \n \n b o d y \n bufsz = 6
350 * f o o : b a r \n b o d y \n bufsz = 4
353 * For the normal case, bufsz includes the
354 * header-terminating `\n', even though it is not in buf,
355 * but bufsz isn't affected when it's missing in the unusual
357 if (name
[0] == ':') {
358 ct
->c_begin
= ftell(in
) - bufsz
;
360 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
365 ct
->c_begin
= ftell (in
);
370 die("message format error in component #%d", compnum
);
373 die("getfld() returned %d", state
);
376 /* break out of the loop */
379 m_getfld_state_destroy (&gstate
);
382 * Read the content headers. We will parse the
383 * MIME related header fields into their various
384 * structures and set internal flags related to
385 * content type/subtype, etc.
388 hp
= ct
->c_first_hf
; /* start at first header field */
390 /* Get MIME-Version field */
391 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
396 vrsn
= mh_xstrdup(FENDNULL(hp
->value
));
398 /* Now, cleanup this field */
401 while (isspace ((unsigned char) *cp
))
403 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
405 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
406 if (!isspace ((unsigned char) *dp
))
410 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
413 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
416 for (dp
= cp
; istoken (*dp
); dp
++)
420 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
423 inform("message %s has unknown value for %s: field (%s), continuing...",
424 ct
->c_file
, VRSN_FIELD
, cp
);
429 if (! suppress_multiple_mime_version_warning
)
430 inform("message %s has multiple %s: fields",
431 ct
->c_file
, VRSN_FIELD
);
435 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
436 /* Get Content-Type field */
437 struct str2init
*s2i
;
438 CI ci
= &ct
->c_ctinfo
;
440 /* Check if we've already seen a Content-Type header */
442 inform("message %s has multiple %s: fields",
443 ct
->c_file
, TYPE_FIELD
);
447 /* Parse the Content-Type field */
448 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
452 * Set the Init function and the internal
453 * flag for this content type.
455 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
456 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
458 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
460 ct
->c_type
= s2i
->si_val
;
461 ct
->c_ctinitfnx
= s2i
->si_init
;
463 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
464 /* Get Content-Transfer-Encoding field */
466 struct str2init
*s2i
;
469 * Check if we've already seen the
470 * Content-Transfer-Encoding field
473 inform("message %s has multiple %s: fields",
474 ct
->c_file
, ENCODING_FIELD
);
478 /* get copy of this field */
479 ct
->c_celine
= cp
= mh_xstrdup(FENDNULL(hp
->value
));
481 while (isspace ((unsigned char) *cp
))
483 for (dp
= cp
; istoken (*dp
); dp
++)
489 * Find the internal flag and Init function
490 * for this transfer encoding.
492 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
493 if (!strcasecmp (cp
, s2i
->si_key
))
495 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
498 ct
->c_encoding
= s2i
->si_val
;
500 /* Call the Init function for this encoding */
501 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
504 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
505 /* Get Content-MD5 field */
511 if (ct
->c_digested
) {
512 inform("message %s has multiple %s: fields",
513 ct
->c_file
, MD5_FIELD
);
517 ep
= cp
= mh_xstrdup(FENDNULL(hp
->value
)); /* get a copy */
519 while (isspace ((unsigned char) *cp
))
521 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
523 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
524 if (!isspace ((unsigned char) *dp
))
528 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
531 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
536 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
544 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
545 /* Get Content-ID field */
546 ct
->c_id
= add (hp
->value
, ct
->c_id
);
548 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
549 /* Get Content-Description field */
550 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
552 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
553 /* Get Content-Disposition field */
554 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
559 hp
= hp
->next
; /* next header field */
563 * Check if we saw a Content-Type field.
564 * If not, then assign a default value for
565 * it, and the Init function.
569 * If we are inside a multipart/digest message,
570 * so default type is message/rfc822
573 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
575 ct
->c_type
= CT_MESSAGE
;
576 ct
->c_ctinitfnx
= InitMessage
;
579 * Else default type is text/plain
581 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
583 ct
->c_type
= CT_TEXT
;
584 ct
->c_ctinitfnx
= InitText
;
588 /* Use default Transfer-Encoding, if necessary */
590 ct
->c_encoding
= CE_7BIT
;
603 * small routine to add header field to list
607 add_header (CT ct
, char *name
, char *value
)
611 /* allocate header field structure */
614 /* link data into header structure */
619 /* link header structure into the list */
620 if (ct
->c_first_hf
== NULL
) {
621 ct
->c_first_hf
= hp
; /* this is the first */
624 ct
->c_last_hf
->next
= hp
; /* add it to the end */
633 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
634 * directives. Fills in the information of the CTinfo structure.
637 get_ctinfo (char *cp
, CT ct
, int magic
)
646 /* store copy of Content-Type line */
647 cp
= ct
->c_ctline
= mh_xstrdup(FENDNULL(cp
));
649 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
652 /* change newlines to spaces */
653 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
656 /* trim trailing spaces */
657 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
658 if (!isspace ((unsigned char) *dp
))
663 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
665 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
666 &ci
->ci_comment
) == NOTOK
)
669 for (dp
= cp
; istoken (*dp
); dp
++)
673 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
678 inform("invalid %s: field in message %s (empty type)",
679 TYPE_FIELD
, ct
->c_file
);
682 to_lower(ci
->ci_type
);
684 while (isspace ((unsigned char) *cp
))
687 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
688 &ci
->ci_comment
) == NOTOK
)
693 ci
->ci_subtype
= mh_xstrdup("");
698 while (isspace ((unsigned char) *cp
))
701 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
702 &ci
->ci_comment
) == NOTOK
)
705 for (dp
= cp
; istoken (*dp
); dp
++)
709 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
713 if (!*ci
->ci_subtype
) {
714 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
715 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
718 to_lower(ci
->ci_subtype
);
721 while (isspace ((unsigned char) *cp
))
724 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
725 &ci
->ci_comment
) == NOTOK
)
728 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
729 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
730 &ci
->ci_comment
)) != OK
) {
731 return status
== NOTOK
? NOTOK
: OK
;
735 * Get any <Content-Id> given in buffer
737 if (magic
&& *cp
== '<') {
740 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
741 inform("invalid ID in message %s", ct
->c_file
);
747 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
753 while (isspace ((unsigned char) *cp
))
758 * Get any [Content-Description] given in buffer.
760 if (magic
&& *cp
== '[') {
762 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
766 inform("invalid description in message %s", ct
->c_file
);
774 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
780 while (isspace ((unsigned char) *cp
))
785 * Get any {Content-Disposition} given in buffer.
787 if (magic
&& *cp
== '{') {
789 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
793 inform("invalid disposition in message %s", ct
->c_file
);
801 if (get_dispo(cp
, ct
, 1) != OK
)
807 while (isspace ((unsigned char) *cp
))
812 * Get any extension directives (right now just the content transfer
813 * encoding, but maybe others) that we care about.
816 if (magic
&& *cp
== '*') {
818 * See if it's a CTE we match on
823 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
827 inform("invalid null transfer encoding specification");
834 ct
->c_reqencoding
= CE_UNKNOWN
;
836 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
837 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
838 ct
->c_reqencoding
= kv
->kv_value
;
843 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
844 inform("invalid CTE specification: \"%s\"", dp
);
848 while (isspace ((unsigned char) *cp
))
853 * Check if anything is left over
857 ci
->ci_magic
= mh_xstrdup(cp
);
859 /* If there is a Content-Disposition header and it doesn't
860 have a *filename=, extract it from the magic contents.
861 The r1bindex call skips any leading directory
863 if (ct
->c_dispo_type
&&
864 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
865 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
866 r1bindex(ci
->ci_magic
, '/'), 0);
870 inform("extraneous information in message %s's %s: field\n"
871 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
879 * Parse out a Content-Disposition header. A lot of this is cribbed from
883 get_dispo (char *cp
, CT ct
, int buildflag
)
885 char *dp
, *dispoheader
;
890 * Save the whole copy of the Content-Disposition header, unless we're
891 * processing a mhbuild directive. A NULL c_dispo will be a flag to
892 * mhbuild that the disposition header needs to be generated at that
896 dispoheader
= cp
= mh_xstrdup(FENDNULL(cp
));
898 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
901 /* change newlines to spaces */
902 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
905 /* trim trailing spaces */
906 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
907 if (!isspace ((unsigned char) *dp
))
912 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
914 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
920 for (dp
= cp
; istoken (*dp
); dp
++)
924 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
928 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
931 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
932 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
934 if (status
== NOTOK
) {
939 inform("extraneous information in message %s's %s: field\n (%s)",
940 ct
->c_file
, DISPO_FIELD
, cp
);
946 ct
->c_dispo
= dispoheader
;
953 get_comment (const char *filename
, const char *fieldname
, char **ap
,
958 char c
, buffer
[BUFSIZ
], *dp
;
968 inform("invalid comment in message %s's %s: field",
969 filename
, fieldname
);
974 if ((c
= *cp
++) == '\0')
997 if ((dp
= *commentp
)) {
998 *commentp
= concat (dp
, " ", buffer
, NULL
);
1001 *commentp
= mh_xstrdup(buffer
);
1005 while (isspace ((unsigned char) *cp
))
1016 * Handles content types audio, image, and video.
1017 * There's not much to do right here.
1025 return OK
; /* not much to do here */
1036 char buffer
[BUFSIZ
];
1041 CI ci
= &ct
->c_ctinfo
;
1043 /* check for missing subtype */
1044 if (!*ci
->ci_subtype
)
1045 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1048 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1050 /* allocate text character set structure */
1052 ct
->c_ctparams
= (void *) t
;
1054 /* scan for charset parameter */
1055 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1056 if (!strcasecmp (pm
->pm_name
, "charset"))
1059 /* check if content specified a character set */
1061 chset
= pm
->pm_value
;
1062 t
->tx_charset
= CHARSET_SPECIFIED
;
1064 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1068 * If we can not handle character set natively,
1069 * then check profile for string to modify the
1070 * terminal or display method.
1072 * termproc is for mhshow, though mhlist -debug prints it, too.
1074 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1075 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1076 if ((cp
= context_find (buffer
)))
1077 ct
->c_termproc
= mh_xstrdup(cp
);
1089 InitMultiPart (CT ct
)
1099 struct multipart
*m
;
1100 struct part
*part
, **next
;
1101 CI ci
= &ct
->c_ctinfo
;
1106 * The encoding for multipart messages must be either
1107 * 7bit, 8bit, or binary (per RFC 2045).
1109 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1110 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1111 /* Copy the Content-Transfer-Encoding header field body so we can
1112 remove any trailing whitespace and leading blanks from it. */
1113 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1115 bp
= cte
+ strlen (cte
) - 1;
1116 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1117 for (bp
= cte
; isblank((unsigned char)*bp
); ++bp
) continue;
1119 inform("\"%s/%s\" type in message %s must be encoded in\n"
1120 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1121 "mhfixmsg -fixcte can fix it, or\n"
1122 "manually edit the file and change the \"%s\"\n"
1123 "Content-Transfer-Encoding to one of those. For now, continuing...",
1124 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1131 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1134 * Check for "boundary" parameter, which is
1135 * required for multipart messages.
1138 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1139 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1145 /* complain if boundary parameter is missing */
1147 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1148 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1152 /* allocate primary structure for multipart info */
1154 ct
->c_ctparams
= (void *) m
;
1156 /* check if boundary parameter contains only whitespace characters */
1157 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1160 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1161 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1165 /* remove trailing whitespace from boundary parameter */
1166 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1167 if (!isspace ((unsigned char) *dp
))
1171 /* record boundary separators */
1172 m
->mp_start
= concat (bp
, "\n", NULL
);
1173 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1175 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1176 advise (ct
->c_file
, "unable to open for reading");
1180 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1182 next
= &m
->mp_parts
;
1186 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1191 if (bufp
[0] != '-' || bufp
[1] != '-')
1194 if (strcmp (bufp
+ 2, m
->mp_start
))
1199 next
= &part
->mp_next
;
1201 if (!(p
= get_content (fp
, ct
->c_file
,
1202 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1210 fseek (fp
, pos
, SEEK_SET
);
1213 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1217 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1218 if (p
->c_end
< p
->c_begin
)
1219 p
->c_begin
= p
->c_end
;
1224 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1229 if (! suppress_bogus_mp_content_warning
) {
1230 inform("bogus multipart content in message %s", ct
->c_file
);
1232 bogus_mp_content
= true;
1234 if (!inout
&& part
) {
1236 p
->c_end
= ct
->c_end
;
1238 if (p
->c_begin
>= p
->c_end
) {
1239 for (next
= &m
->mp_parts
; *next
!= part
;
1240 next
= &((*next
)->mp_next
))
1249 /* reverse the order of the parts for multipart/alternative */
1250 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1256 * label all subparts with part number, and
1257 * then initialize the content of the subpart.
1262 char partnam
[BUFSIZ
];
1265 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1266 pp
= partnam
+ strlen (partnam
);
1271 for (part
= m
->mp_parts
, partnum
= 1; part
;
1272 part
= part
->mp_next
, partnum
++) {
1275 sprintf (pp
, "%d", partnum
);
1276 p
->c_partno
= mh_xstrdup(partnam
);
1278 /* initialize the content of the subparts */
1279 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1288 get_leftover_mp_content (ct
, 1);
1289 get_leftover_mp_content (ct
, 0);
1299 * reverse the order of the parts of a multipart/alternative,
1300 * presumably to put the "most favored" alternative first, for
1301 * ease of choosing/displaying it later on. from a mail message on
1302 * nmh-workers, from kenh:
1303 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1304 * see code in mhn that did the same thing... According to the RCS
1305 * logs, that code was around from the initial checkin of mhn.c by
1306 * John Romine in 1992, which is as far back as we have."
1309 reverse_parts (CT ct
)
1311 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1315 /* Reverse the order of its parts by walking the mp_parts list
1316 and pushing each node to the front. */
1317 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1318 next
= part
->mp_next
;
1319 part
->mp_next
= m
->mp_parts
;
1325 move_preferred_part(CT ct
, mime_type_subtype
*pref
)
1327 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1328 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1332 /* move the matching part(s) to the head of the list: walk the
1333 * list of parts, move matching parts to a new list (maintaining
1334 * their order), and finally, concatenate the old list onto the
1341 head
->mp_next
= m
->mp_parts
;
1342 nhead
->mp_next
= NULL
;
1346 part
= head
->mp_next
;
1347 while (part
!= NULL
) {
1348 ci
= &part
->mp_part
->c_ctinfo
;
1349 if (!strcasecmp(ci
->ci_type
, pref
->type
) &&
1351 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1352 prev
->mp_next
= part
->mp_next
;
1353 part
->mp_next
= NULL
;
1354 ntail
->mp_next
= part
;
1356 part
= prev
->mp_next
;
1359 part
= prev
->mp_next
;
1362 ntail
->mp_next
= head
->mp_next
;
1363 m
->mp_parts
= nhead
->mp_next
;
1367 * move parts that match the user's preferences (-prefer) to the head
1368 * of the line. process preferences in reverse so first one given
1369 * ends up first in line
1375 for (i
= 0; i
< npreferred
; i
++)
1376 move_preferred_part(ct
, mime_preference
+ i
);
1381 /* parse_mime() arranges alternates in reverse (priority) order. This
1382 function can be used to reverse them back. This will put, for
1383 example, a text/plain part before a text/html part in a
1384 multipart/alternative part, for example, where it belongs. */
1386 reverse_alternative_parts (CT ct
)
1388 if (ct
->c_type
== CT_MULTIPART
) {
1389 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1392 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1396 /* And call recursively on each part of a multipart. */
1397 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1398 reverse_alternative_parts (part
->mp_part
);
1411 CI ci
= &ct
->c_ctinfo
;
1413 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1414 inform("\"%s/%s\" type in message %s should be encoded in "
1415 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1420 /* check for missing subtype */
1421 if (!*ci
->ci_subtype
)
1422 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1425 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1427 switch (ct
->c_subtype
) {
1428 case MESSAGE_RFC822
:
1431 case MESSAGE_PARTIAL
:
1437 ct
->c_ctparams
= (void *) p
;
1439 /* scan for parameters "id", "number", and "total" */
1440 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1441 if (!strcasecmp (pm
->pm_name
, "id")) {
1442 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1445 if (!strcasecmp (pm
->pm_name
, "number")) {
1446 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1447 || p
->pm_partno
< 1) {
1449 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1450 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1451 ct
->c_file
, TYPE_FIELD
);
1456 if (!strcasecmp (pm
->pm_name
, "total")) {
1457 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1466 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1467 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1468 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1474 case MESSAGE_EXTERNAL
:
1482 ct
->c_ctparams
= (void *) e
;
1485 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1486 advise (ct
->c_file
, "unable to open for reading");
1490 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1492 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1500 p
->c_ceopenfnx
= NULL
;
1501 if ((exresult
= params_external (ct
, 0)) != NOTOK
1502 && p
->c_ceopenfnx
== openMail
) {
1506 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1508 content_error (NULL
, ct
,
1509 "empty body for access-type=mail-server");
1513 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1514 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1516 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1518 adios ("failed", "fread");
1521 die("unexpected EOF from fread");
1524 bp
+= cc
, size
-= cc
;
1531 p
->c_end
= p
->c_begin
;
1536 if (exresult
== NOTOK
)
1538 if (e
->eb_flags
== NOTOK
)
1541 switch (p
->c_type
) {
1546 if (p
->c_subtype
!= MESSAGE_RFC822
)
1550 e
->eb_partno
= ct
->c_partno
;
1552 (*p
->c_ctinitfnx
) (p
);
1567 params_external (CT ct
, int composing
)
1570 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1571 CI ci
= &ct
->c_ctinfo
;
1573 ct
->c_ceopenfnx
= NULL
;
1574 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1575 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1576 struct str2init
*s2i
;
1577 CT p
= e
->eb_content
;
1579 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1580 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1583 e
->eb_access
= pm
->pm_value
;
1584 e
->eb_flags
= NOTOK
;
1585 p
->c_encoding
= CE_EXTERNAL
;
1588 e
->eb_access
= s2i
->si_key
;
1589 e
->eb_flags
= s2i
->si_val
;
1590 p
->c_encoding
= CE_EXTERNAL
;
1592 /* Call the Init function for this external type */
1593 if ((*s2i
->si_init
)(p
) == NOTOK
)
1597 if (!strcasecmp (pm
->pm_name
, "name")) {
1598 e
->eb_name
= pm
->pm_value
;
1601 if (!strcasecmp (pm
->pm_name
, "permission")) {
1602 e
->eb_permission
= pm
->pm_value
;
1605 if (!strcasecmp (pm
->pm_name
, "site")) {
1606 e
->eb_site
= pm
->pm_value
;
1609 if (!strcasecmp (pm
->pm_name
, "directory")) {
1610 e
->eb_dir
= pm
->pm_value
;
1613 if (!strcasecmp (pm
->pm_name
, "mode")) {
1614 e
->eb_mode
= pm
->pm_value
;
1617 if (!strcasecmp (pm
->pm_name
, "size")) {
1618 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1621 if (!strcasecmp (pm
->pm_name
, "server")) {
1622 e
->eb_server
= pm
->pm_value
;
1625 if (!strcasecmp (pm
->pm_name
, "subject")) {
1626 e
->eb_subject
= pm
->pm_value
;
1629 if (!strcasecmp (pm
->pm_name
, "url")) {
1631 * According to RFC 2017, we have to remove all whitespace from
1635 char *u
, *p
= pm
->pm_value
;
1636 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1638 for (; *p
!= '\0'; p
++) {
1639 if (! isspace((unsigned char) *p
))
1646 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1647 e
->eb_body
= getcpy (pm
->pm_value
);
1652 if (!e
->eb_access
) {
1653 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1654 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1667 InitApplication (CT ct
)
1669 CI ci
= &ct
->c_ctinfo
;
1672 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1679 * TRANSFER ENCODINGS
1683 init_encoding (CT ct
, OpenCEFunc openfnx
)
1685 ct
->c_ceopenfnx
= openfnx
;
1686 ct
->c_ceclosefnx
= close_encoding
;
1687 ct
->c_cesizefnx
= size_encoding
;
1694 close_encoding (CT ct
)
1696 CE ce
= &ct
->c_cefile
;
1705 static unsigned long
1706 size_encoding (CT ct
)
1711 CE ce
= &ct
->c_cefile
;
1714 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1715 return (long) st
.st_size
;
1718 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1719 return (long) st
.st_size
;
1723 if (ct
->c_encoding
== CE_EXTERNAL
)
1724 return ct
->c_end
- ct
->c_begin
;
1727 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1728 return ct
->c_end
- ct
->c_begin
;
1730 if (fstat (fd
, &st
) != NOTOK
)
1731 size
= (long) st
.st_size
;
1735 (*ct
->c_ceclosefnx
) (ct
);
1747 return init_encoding (ct
, openBase64
);
1752 openBase64 (CT ct
, char **file
)
1756 bool own_ct_fp
= false;
1757 char *cp
, *buffer
= NULL
;
1758 /* sbeck -- handle suffixes */
1760 CE ce
= &ct
->c_cefile
;
1761 unsigned char *decoded
;
1763 unsigned char digest
[16];
1766 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1771 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1772 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1778 if (*file
== NULL
) {
1781 ce
->ce_file
= mh_xstrdup(*file
);
1785 /* sbeck@cise.ufl.edu -- handle suffixes */
1787 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1788 if (ce
->ce_unlink
) {
1789 /* Create temporary file with filename extension. */
1790 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1791 die("unable to create temporary file in %s",
1795 ce
->ce_file
= add (cp
, ce
->ce_file
);
1797 } else if (*file
== NULL
) {
1799 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1800 die("unable to create temporary file in %s",
1803 ce
->ce_file
= mh_xstrdup(tempfile
);
1806 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1807 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1811 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1812 die("internal error(1)");
1814 buffer
= mh_xmalloc (len
+ 1);
1817 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1818 content_error (ct
->c_file
, ct
, "unable to open for reading");
1824 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1827 switch (cc
= read (fd
, cp
, len
)) {
1829 content_error (ct
->c_file
, ct
, "error reading from");
1833 content_error (NULL
, ct
, "premature eof");
1844 /* decodeBase64() requires null-terminated input. */
1847 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1848 ct
->c_digested
? digest
: NULL
) != OK
)
1853 unsigned char *decoded_p
= decoded
;
1854 for (i
= 0; i
< decoded_len
; ++i
) {
1855 putc (*decoded_p
++, ce
->ce_fp
);
1858 if (ferror (ce
->ce_fp
)) {
1859 content_error (ce
->ce_file
, ct
, "error writing to");
1863 if (ct
->c_digested
) {
1864 if (memcmp(digest
, ct
->c_digest
,
1866 content_error (NULL
, ct
,
1867 "content integrity suspect (digest mismatch) -- continuing");
1870 fprintf (stderr
, "content integrity confirmed\n");
1876 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1878 if (fflush (ce
->ce_fp
)) {
1879 content_error (ce
->ce_file
, ct
, "error writing to");
1883 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1886 *file
= ce
->ce_file
;
1892 return fileno (ce
->ce_fp
);
1899 free_encoding (ct
, 0);
1909 static char hex2nib
[0x80] = {
1910 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1911 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1913 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1914 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1915 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1916 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1917 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1918 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1919 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1920 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1921 0x00, 0x00, 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
1932 return init_encoding (ct
, openQuoted
);
1937 openQuoted (CT ct
, char **file
)
1939 int cc
, digested
, len
, quoted
;
1940 bool own_ct_fp
= false;
1946 CE ce
= &ct
->c_cefile
;
1947 /* sbeck -- handle suffixes */
1952 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1957 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1958 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1964 if (*file
== NULL
) {
1967 ce
->ce_file
= mh_xstrdup(*file
);
1971 /* sbeck@cise.ufl.edu -- handle suffixes */
1973 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1974 if (ce
->ce_unlink
) {
1975 /* Create temporary file with filename extension. */
1976 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1977 die("unable to create temporary file in %s",
1981 ce
->ce_file
= add (cp
, ce
->ce_file
);
1983 } else if (*file
== NULL
) {
1985 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1986 die("unable to create temporary file in %s",
1989 ce
->ce_file
= mh_xstrdup(tempfile
);
1992 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1993 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1997 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1998 die("internal error(2)");
2001 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2002 content_error (ct
->c_file
, ct
, "unable to open for reading");
2008 if ((digested
= ct
->c_digested
))
2009 MD5Init (&mdContext
);
2013 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2015 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2016 content_error (NULL
, ct
, "premature eof");
2020 if ((cc
= gotlen
) > len
)
2024 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2025 if (!isspace ((unsigned char) *ep
))
2030 for (; cp
< ep
; cp
++) {
2032 /* in an escape sequence */
2034 /* at byte 1 of an escape sequence */
2035 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2036 /* next is byte 2 */
2039 /* at byte 2 of an escape sequence */
2041 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2042 putc (mask
, ce
->ce_fp
);
2044 MD5Update (&mdContext
, &mask
, 1);
2045 if (ferror (ce
->ce_fp
)) {
2046 content_error (ce
->ce_file
, ct
, "error writing to");
2049 /* finished escape sequence; next may be literal or a new
2050 * escape sequence */
2053 /* on to next byte */
2057 /* not in an escape sequence */
2059 /* starting an escape sequence, or invalid '='? */
2060 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2061 /* "=\n" soft line break, eat the \n */
2065 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2066 /* We don't have 2 bytes left, so this is an invalid
2067 * escape sequence; just show the raw bytes (below). */
2068 } else if (isxdigit ((unsigned char) cp
[1]) &&
2069 isxdigit ((unsigned char) cp
[2])) {
2070 /* Next 2 bytes are hex digits, making this a valid escape
2071 * sequence; let's decode it (above). */
2075 /* One or both of the next 2 is out of range, making this
2076 * an invalid escape sequence; just show the raw bytes
2080 /* Just show the raw byte. */
2081 putc (*cp
, ce
->ce_fp
);
2084 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2086 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2089 if (ferror (ce
->ce_fp
)) {
2090 content_error (ce
->ce_file
, ct
, "error writing to");
2096 content_error (NULL
, ct
,
2097 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2101 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2103 if (fflush (ce
->ce_fp
)) {
2104 content_error (ce
->ce_file
, ct
, "error writing to");
2109 unsigned char digest
[16];
2111 MD5Final (digest
, &mdContext
);
2112 if (memcmp(digest
, ct
->c_digest
,
2114 content_error (NULL
, ct
,
2115 "content integrity suspect (digest mismatch) -- continuing");
2117 fprintf (stderr
, "content integrity confirmed\n");
2120 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2123 *file
= ce
->ce_file
;
2129 return fileno (ce
->ce_fp
);
2132 free_encoding (ct
, 0);
2149 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2152 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2158 open7Bit (CT ct
, char **file
)
2161 bool own_ct_fp
= false;
2162 char buffer
[BUFSIZ
];
2163 /* sbeck -- handle suffixes */
2166 CE ce
= &ct
->c_cefile
;
2169 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2174 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2175 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2181 if (*file
== NULL
) {
2184 ce
->ce_file
= mh_xstrdup(*file
);
2188 /* sbeck@cise.ufl.edu -- handle suffixes */
2190 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2191 if (ce
->ce_unlink
) {
2192 /* Create temporary file with filename extension. */
2193 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2194 die("unable to create temporary file in %s",
2198 ce
->ce_file
= add (cp
, ce
->ce_file
);
2200 } else if (*file
== NULL
) {
2202 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2203 die("unable to create temporary file in %s",
2206 ce
->ce_file
= mh_xstrdup(tempfile
);
2209 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2210 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2214 if (ct
->c_type
== CT_MULTIPART
) {
2215 CI ci
= &ct
->c_ctinfo
;
2219 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2220 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2221 + 1 + strlen (ci
->ci_subtype
);
2222 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2225 fputs (buffer
, ce
->ce_fp
);
2229 if (ci
->ci_comment
) {
2230 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2231 fputs ("\n\t", ce
->ce_fp
);
2235 putc (' ', ce
->ce_fp
);
2238 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2241 fprintf (ce
->ce_fp
, "\n");
2243 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2245 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2247 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2248 fprintf (ce
->ce_fp
, "\n");
2251 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2252 die("internal error(3)");
2255 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2256 content_error (ct
->c_file
, ct
, "unable to open for reading");
2262 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2264 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2266 content_error (ct
->c_file
, ct
, "error reading from");
2270 content_error (NULL
, ct
, "premature eof");
2278 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2279 advise ("open7Bit", "fwrite");
2281 if (ferror (ce
->ce_fp
)) {
2282 content_error (ce
->ce_file
, ct
, "error writing to");
2287 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2289 if (fflush (ce
->ce_fp
)) {
2290 content_error (ce
->ce_file
, ct
, "error writing to");
2294 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2297 *file
= ce
->ce_file
;
2302 return fileno (ce
->ce_fp
);
2305 free_encoding (ct
, 0);
2319 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2321 char cachefile
[BUFSIZ
];
2324 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2329 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2330 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2336 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2337 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2338 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2339 ce
->ce_file
= mh_xstrdup(cachefile
);
2343 admonish (cachefile
, "unable to fopen for reading");
2346 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2350 *file
= ce
->ce_file
;
2351 *fd
= fileno (ce
->ce_fp
);
2362 return init_encoding (ct
, openFile
);
2367 openFile (CT ct
, char **file
)
2370 char cachefile
[BUFSIZ
];
2371 struct exbody
*e
= ct
->c_ctexbody
;
2372 CE ce
= &ct
->c_cefile
;
2374 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2386 content_error (NULL
, ct
, "missing name parameter");
2390 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2393 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2394 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2398 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2399 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2400 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2404 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2405 if ((fp
= fopen (cachefile
, "w"))) {
2407 char buffer
[BUFSIZ
];
2408 FILE *gp
= ce
->ce_fp
;
2410 fseek (gp
, 0L, SEEK_SET
);
2412 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2414 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2415 advise ("openFile", "fwrite");
2420 admonish (ce
->ce_file
, "error reading");
2421 (void) m_unlink (cachefile
);
2422 } else if (ferror (fp
)) {
2423 admonish (cachefile
, "error writing");
2424 (void) m_unlink (cachefile
);
2431 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2432 *file
= ce
->ce_file
;
2433 return fileno (ce
->ce_fp
);
2443 return init_encoding (ct
, openFTP
);
2448 openFTP (CT ct
, char **file
)
2454 char *bp
, *ftp
, *user
, *pass
;
2455 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2457 CE ce
= &ct
->c_cefile
;
2458 static char *username
= NULL
;
2459 static char *password
= NULL
;
2463 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2469 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2480 if (!e
->eb_name
|| !e
->eb_site
) {
2481 content_error (NULL
, ct
, "missing %s parameter",
2482 e
->eb_name
? "site": "name");
2486 /* Get the buffer ready to go */
2488 buflen
= sizeof(buffer
);
2491 * Construct the query message for user
2493 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2499 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2505 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2506 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2511 if (e
->eb_size
> 0) {
2512 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2517 snprintf (bp
, buflen
, "? ");
2520 * Now, check the answer
2522 if (!read_yes_or_no_if_tty (buffer
))
2527 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2531 ruserpass (e
->eb_site
, &username
, &password
, 0);
2536 ce
->ce_unlink
= (*file
== NULL
);
2538 cachefile
[0] = '\0';
2539 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2540 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2541 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2542 if (*file
== NULL
) {
2549 ce
->ce_file
= mh_xstrdup(*file
);
2551 ce
->ce_file
= mh_xstrdup(cachefile
);
2554 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2555 die("unable to create temporary file in %s",
2558 ce
->ce_file
= mh_xstrdup(tempfile
);
2561 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2562 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2571 vec
[vecp
++] = r1bindex (ftp
, '/');
2572 vec
[vecp
++] = e
->eb_site
;
2575 vec
[vecp
++] = e
->eb_dir
;
2576 vec
[vecp
++] = e
->eb_name
;
2577 vec
[vecp
++] = ce
->ce_file
,
2578 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2579 ? "ascii" : "binary";
2587 adios ("fork", "unable to");
2591 close (fileno (ce
->ce_fp
));
2593 fprintf (stderr
, "unable to exec ");
2599 if (pidXwait (child_id
, NULL
)) {
2600 username
= password
= NULL
;
2610 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2615 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2616 if ((fp
= fopen (cachefile
, "w"))) {
2618 FILE *gp
= ce
->ce_fp
;
2620 fseek (gp
, 0L, SEEK_SET
);
2622 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2624 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2625 advise ("openFTP", "fwrite");
2630 admonish (ce
->ce_file
, "error reading");
2631 (void) m_unlink (cachefile
);
2632 } else if (ferror (fp
)) {
2633 admonish (cachefile
, "error writing");
2634 (void) m_unlink (cachefile
);
2642 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2643 *file
= ce
->ce_file
;
2644 return fileno (ce
->ce_fp
);
2655 return init_encoding (ct
, openMail
);
2660 openMail (CT ct
, char **file
)
2662 int child_id
, fd
, vecp
;
2664 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2665 struct exbody
*e
= ct
->c_ctexbody
;
2666 CE ce
= &ct
->c_cefile
;
2668 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2679 if (!e
->eb_server
) {
2680 content_error (NULL
, ct
, "missing server parameter");
2684 /* Get buffer ready to go */
2686 buflen
= sizeof(buffer
);
2688 /* Now, construct query message */
2689 snprintf (bp
, buflen
, "Retrieve content");
2695 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2701 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2703 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2705 /* Now, check answer */
2706 if (!read_yes_or_no_if_tty (buffer
))
2710 vec
[vecp
++] = r1bindex (mailproc
, '/');
2711 vec
[vecp
++] = e
->eb_server
;
2712 vec
[vecp
++] = "-subject";
2713 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2714 vec
[vecp
++] = "-body";
2715 vec
[vecp
++] = e
->eb_body
;
2721 advise ("fork", "unable to");
2725 execvp (mailproc
, vec
);
2726 fprintf (stderr
, "unable to exec ");
2732 if (pidXwait (child_id
, NULL
) == OK
)
2733 inform("request sent");
2737 if (*file
== NULL
) {
2739 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2740 die("unable to create temporary file in %s",
2743 ce
->ce_file
= mh_xstrdup(tempfile
);
2746 ce
->ce_file
= mh_xstrdup(*file
);
2750 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2751 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2755 /* showproc is for mhshow and mhstore, though mhlist -debug
2756 * prints it, too. */
2757 free(ct
->c_showproc
);
2758 ct
->c_showproc
= mh_xstrdup("true");
2760 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2761 *file
= ce
->ce_file
;
2762 return fileno (ce
->ce_fp
);
2773 return init_encoding (ct
, openURL
);
2778 openURL (CT ct
, char **file
)
2780 struct exbody
*e
= ct
->c_ctexbody
;
2781 CE ce
= &ct
->c_cefile
;
2782 char *urlprog
, *program
;
2783 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2787 struct msgs_array args
= { 0, 0, NULL
};
2790 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2794 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2798 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2810 content_error(NULL
, ct
, "missing url parameter");
2814 ce
->ce_unlink
= (*file
== NULL
);
2816 cachefile
[0] = '\0';
2818 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2819 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2820 if (*file
== NULL
) {
2827 ce
->ce_file
= mh_xstrdup(*file
);
2829 ce
->ce_file
= mh_xstrdup(cachefile
);
2832 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2833 die("unable to create temporary file in %s",
2836 ce
->ce_file
= mh_xstrdup(tempfile
);
2839 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2840 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2844 switch (child_id
= fork()) {
2846 adios ("fork", "unable to");
2850 argsplit_msgarg(&args
, urlprog
, &program
);
2851 app_msgarg(&args
, e
->eb_url
);
2852 app_msgarg(&args
, NULL
);
2853 dup2(fileno(ce
->ce_fp
), 1);
2854 close(fileno(ce
->ce_fp
));
2855 execvp(program
, args
.msgs
);
2856 fprintf(stderr
, "Unable to exec ");
2862 if (pidXwait(child_id
, NULL
)) {
2870 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2875 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2876 if ((fp
= fopen(cachefile
, "w"))) {
2878 FILE *gp
= ce
->ce_fp
;
2880 fseeko(gp
, 0, SEEK_SET
);
2882 while ((cc
= fread(buffer
, sizeof(*buffer
),
2883 sizeof(buffer
), gp
)) > 0)
2884 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2885 advise ("openURL", "fwrite");
2891 admonish(ce
->ce_file
, "error reading");
2892 (void) m_unlink (cachefile
);
2899 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2900 *file
= ce
->ce_file
;
2901 return fileno(ce
->ce_fp
);
2906 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2907 * has to be base64 decoded.
2910 readDigest (CT ct
, char *cp
)
2912 unsigned char *digest
;
2915 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2916 const size_t maxlen
= sizeof ct
->c_digest
;
2918 if (strlen ((char *) digest
) <= maxlen
) {
2919 memcpy (ct
->c_digest
, digest
, maxlen
);
2924 fprintf (stderr
, "MD5 digest=");
2925 for (i
= 0; i
< maxlen
; ++i
) {
2926 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2928 fprintf (stderr
, "\n");
2934 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2935 (int) strlen ((char *) digest
));
2945 /* Multipart parts might have content before the first subpart and/or
2946 after the last subpart that hasn't been stored anywhere else, so do
2949 get_leftover_mp_content (CT ct
, int before
/* or after */)
2951 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2953 bool found_boundary
= false;
2959 char *content
= NULL
;
2961 if (! m
) return NOTOK
;
2964 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2966 /* Isolate the beginning of this part to the beginning of the
2967 first subpart and save any content between them. */
2968 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2969 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2970 boundary
= concat ("--", m
->mp_start
, NULL
);
2972 struct part
*last_subpart
= NULL
;
2973 struct part
*subpart
;
2975 /* Go to the last subpart to get its end position. */
2976 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2977 last_subpart
= subpart
;
2980 if (last_subpart
== NULL
) return NOTOK
;
2982 /* Isolate the end of the last subpart to the end of this part
2983 and save any content between them. */
2984 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2985 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2986 boundary
= concat ("--", m
->mp_stop
, NULL
);
2989 /* Back up by 1 to pick up the newline. */
2990 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2992 /* Don't look beyond beginning of first subpart (before) or
2993 next part (after). */
2994 if (read
> max
) bufp
[read
-max
] = '\0';
2997 if (! strcmp (bufp
, boundary
)) {
2998 found_boundary
= true;
3001 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3002 found_boundary
= true;
3007 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3009 char *old_content
= content
;
3010 content
= concat (content
, bufp
, NULL
);
3014 ? concat ("\n", bufp
, NULL
)
3015 : concat (bufp
, NULL
);
3020 if (found_boundary
|| read
> max
) break;
3022 if (read
> max
) break;
3026 /* Skip the newline if that's all there is. */
3030 /* Remove trailing newline, except at EOF. */
3031 if ((before
|| ! feof (ct
->c_fp
)) &&
3032 (cp
= content
+ strlen (content
)) > content
&&
3037 if (strlen (content
) > 1) {
3039 m
->mp_content_before
= content
;
3041 m
->mp_content_after
= content
;
3056 ct_type_str (int type
)
3059 case CT_APPLICATION
:
3060 return "application";
3076 return "unknown_type";
3082 ct_subtype_str (int type
, int subtype
)
3085 case CT_APPLICATION
:
3087 case APPLICATION_OCTETS
:
3089 case APPLICATION_POSTSCRIPT
:
3090 return "postscript";
3092 return "unknown_app_subtype";
3096 case MESSAGE_RFC822
:
3098 case MESSAGE_PARTIAL
:
3100 case MESSAGE_EXTERNAL
:
3103 return "unknown_msg_subtype";
3109 case MULTI_ALTERNATE
:
3110 return "alternative";
3113 case MULTI_PARALLEL
:
3118 return "unknown_multipart_subtype";
3129 return "unknown_text_subtype";
3132 return "unknown_type";
3138 ct_str_type (const char *type
)
3140 struct str2init
*s2i
;
3142 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3143 if (! strcasecmp (type
, s2i
->si_key
)) {
3147 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3156 ct_str_subtype (int type
, const char *subtype
)
3161 case CT_APPLICATION
:
3162 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3163 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3167 return kv
->kv_value
;
3169 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3170 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3174 return kv
->kv_value
;
3176 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3177 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3181 return kv
->kv_value
;
3183 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3184 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3188 return kv
->kv_value
;
3195 /* Find the content type and InitFunc for the CT. */
3196 const struct str2init
*
3197 get_ct_init (int type
)
3199 const struct str2init
*sp
;
3201 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3202 if (type
== sp
->si_val
) {
3211 ce_str (int encoding
)
3217 return "quoted-printable";
3233 /* Find the content type and InitFunc for the content encoding method. */
3234 const struct str2init
*
3235 get_ce_method (const char *method
)
3237 struct str2init
*sp
;
3239 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3240 if (! strcasecmp (method
, sp
->si_key
)) {
3249 * Parse a series of MIME attributes (or parameters) given a header as
3252 * Arguments include:
3254 * filename - Name of input file (for error messages)
3255 * fieldname - Name of field being processed
3256 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3257 * Updated to point to end of attributes when finished.
3258 * param_head - Pointer to head of parameter list
3259 * param_tail - Pointer to tail of parameter list
3260 * commentp - Pointer to header comment pointer (may be NULL)
3262 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3263 * DONE to indicate a benign error (minor parsing error, but the program
3268 parse_header_attrs (const char *filename
, const char *fieldname
,
3269 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3272 char *cp
= *header_attrp
;
3278 struct sectlist
*next
;
3284 struct sectlist
*sechead
;
3285 struct parmlist
*next
;
3286 } *pp
, *pp2
, *phead
= NULL
;
3288 while (*cp
== ';') {
3289 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3290 bool encoded
= false;
3291 bool partial
= false;
3292 int len
= 0, index
= 0;
3295 while (isspace ((unsigned char) *cp
))
3299 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3304 if (! suppress_extraneous_trailing_semicolon_warning
) {
3305 inform("extraneous trailing ';' in message %s's %s: "
3306 "parameter list", filename
, fieldname
);
3311 /* down case the attribute name */
3312 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3313 *dp
= tolower ((unsigned char) *dp
);
3315 for (up
= dp
; isspace ((unsigned char) *dp
);)
3317 if (dp
== cp
|| *dp
!= '=') {
3318 inform("invalid parameter in message %s's %s: field\n"
3319 " parameter %s (error detected at offset %ld)",
3320 filename
, fieldname
, cp
, (long)(dp
- cp
));
3325 * To handle RFC 2231, we have to deal with the following extensions:
3327 * name*=encoded-value
3328 * name*<N>=part-N-of-a-parameter-value
3329 * name*<N>*=encoded-part-N-of-a-parameter-value
3332 * If there's a * right before the equal sign, it's encoded.
3333 * If there's a * and one or more digits, then it's section N.
3335 * Remember we can have one or the other, or both. cp points to
3336 * beginning of name, up points past the last character in the
3340 for (vp
= cp
; vp
< up
; vp
++) {
3341 if (*vp
== '*' && vp
< up
- 1) {
3345 if (*vp
== '*' && vp
== up
- 1) {
3347 } else if (partial
) {
3348 if (isdigit((unsigned char) *vp
))
3349 index
= *vp
- '0' + index
* 10;
3351 inform("invalid parameter index in message %s's %s: field"
3352 "\n (parameter %s)", filename
, fieldname
, cp
);
3361 * Break out the parameter name and value sections and allocate
3365 nameptr
= mh_xmalloc(len
+ 1);
3366 strncpy(nameptr
, cp
, len
);
3367 nameptr
[len
] = '\0';
3369 for (dp
++; isspace ((unsigned char) *dp
);)
3374 * Single quotes delimit the character set and language tag.
3375 * They are required on the first section (or a complete
3380 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3386 charset
= mh_xmalloc(len
+ 1);
3387 strncpy(charset
, dp
, len
);
3388 charset
[len
] = '\0';
3394 inform("missing charset in message %s's %s: field\n"
3395 " (parameter %s)", filename
, fieldname
, nameptr
);
3401 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3408 lang
= mh_xmalloc(len
+ 1);
3409 strncpy(lang
, dp
, len
);
3416 inform("missing language tag in message %s's %s: field\n"
3417 " (parameter %s)", filename
, fieldname
, nameptr
);
3427 * At this point vp should be pointing at the beginning
3428 * of the encoded value/section. Continue until we reach
3429 * the end or get whitespace. But first, calculate the
3430 * length so we can allocate the correct buffer size.
3433 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3435 if (*(vp
+ 1) == '\0' ||
3436 !isxdigit((unsigned char) *(vp
+ 1)) ||
3437 *(vp
+ 2) == '\0' ||
3438 !isxdigit((unsigned char) *(vp
+ 2))) {
3439 inform("invalid encoded sequence in message %s's %s: field\n"
3440 " (parameter %s)", filename
, fieldname
, nameptr
);
3451 up
= valptr
= mh_xmalloc(len
+ 1);
3453 for (vp
= dp
; istoken(*vp
); vp
++) {
3455 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3466 * A "normal" string. If it's got a leading quote, then we
3467 * strip the quotes out. Otherwise go until we reach the end
3468 * or get whitespace. Note we scan it twice; once to get the
3469 * length, then the second time copies it into the destination
3476 for (cp
= dp
+ 1;;) {
3480 inform("invalid quoted-string in message %s's %s: field\n"
3481 " (parameter %s)", filename
, fieldname
, nameptr
);
3501 for (cp
= dp
; istoken (*cp
); cp
++) {
3506 valptr
= mh_xmalloc(len
+ 1);
3510 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3518 strncpy(valptr
, cp
= dp
, len
);
3526 * If 'partial' is set, we don't allocate a parameter now. We
3527 * put it on the parameter linked list to be reassembled later.
3529 * "phead" points to a list of all parameters we need to reassemble.
3530 * Each parameter has a list of sections. We insert the sections in
3535 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3536 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3551 * Insert this into the section linked list
3559 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3560 sp
->next
= pp
->sechead
;
3563 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3564 if (sp2
->index
== sp
->index
) {
3565 inform("duplicate index (%d) in message %s's %s: field"
3566 "\n (parameter %s)", sp
->index
, filename
,
3567 fieldname
, nameptr
);
3570 if (sp2
->index
< sp
->index
&&
3571 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3572 sp
->next
= sp2
->next
;
3579 inform("Internal error: cannot insert partial param "
3580 "in message %s's %s: field\n (parameter %s)",
3581 filename
, fieldname
, nameptr
);
3587 * Save our charset and lang tags.
3590 if (index
== 0 && encoded
) {
3592 pp
->charset
= charset
;
3597 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3598 pm
->pm_charset
= charset
;
3602 while (isspace ((unsigned char) *cp
))
3606 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3612 * Now that we're done, reassemble all of the partial parameters.
3615 for (pp
= phead
; pp
!= NULL
; ) {
3619 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3620 if (sp
->index
!= pindex
++) {
3621 inform("missing section %d for parameter in message "
3622 "%s's %s: field\n (parameter %s)", pindex
- 1,
3623 filename
, fieldname
, pp
->name
);
3629 p
= q
= mh_xmalloc(tlen
+ 1);
3630 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3631 memcpy(q
, sp
->value
, sp
->len
);
3641 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3642 pm
->pm_charset
= pp
->charset
;
3643 pm
->pm_lang
= pp
->lang
;
3654 * Return the charset for a particular content type.
3658 content_charset (CT ct
)
3660 char *ret_charset
= NULL
;
3662 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3664 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3669 * Create a string based on a list of output parameters. Assume that this
3670 * parameter string will be appended to an existing header, so start out
3671 * with the separator (;). Perform RFC 2231 encoding when necessary.
3675 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3677 char *paramout
= NULL
;
3678 char line
[CPERLIN
* 2], *q
;
3679 int curlen
, index
, cont
, encode
, i
;
3680 size_t valoff
, numchars
;
3682 while (params
!= NULL
) {
3688 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3691 if (strlen(params
->pm_name
) > CPERLIN
) {
3692 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3697 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3700 * Loop until we get a parameter that fits within a line. We
3701 * assume new lines start with a tab, so check our overflow based
3711 * At this point we're definitely continuing the line, so
3712 * be sure to include the parameter name and section index.
3715 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3716 params
->pm_name
, index
);
3719 * Both of these functions do a NUL termination
3723 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3724 numchars
, valoff
, index
);
3726 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3736 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3741 * "line" starts with a ;\n\t, so that doesn't count against
3742 * the length. But add 8 since it starts with a tab; that's
3743 * how we end up with 5.
3746 initialwidth
= strlen(line
) + 5;
3749 * At this point the line should be built, so add it to our
3750 * current output buffer.
3753 paramout
= add(line
, paramout
);
3757 * If this won't fit on the line, start a new one. Save room in
3758 * case we need a semicolon on the end
3761 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3773 * At this point, we're either finishing a continued parameter, or
3774 * we're working on a new one.
3778 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3779 params
->pm_name
, index
);
3781 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3786 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3787 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3789 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3790 strlen(params
->pm_value
+ valoff
), valoff
);
3797 paramout
= add(line
, paramout
);
3798 initialwidth
+= strlen(line
);
3800 params
= params
->pm_next
;
3804 *offsetout
= initialwidth
;
3810 * Calculate the size of a parameter.
3814 * pm - The parameter being output
3815 * index - If continuing the parameter, the index of the section
3817 * valueoff - The current offset into the parameter value that we're
3818 * working on (previous sections have consumed valueoff bytes).
3819 * encode - Set if we should perform encoding on this parameter section
3820 * (given that we're consuming bytesfit bytes).
3821 * cont - Set if the remaining data in value will not fit on a single
3822 * line and will need to be continued.
3823 * bytesfit - The number of bytes that we can consume from the parameter
3824 * value and still fit on a completely new line. The
3825 * calculation assumes the new line starts with a tab,
3826 * includes the parameter name and any encoding, and fits
3827 * within CPERLIN bytes. Will always be at least 1.
3831 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3834 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3835 size_t len
= 0, fit
= 0;
3836 int fitlimit
= 0, eightbit
, maxfit
;
3841 * Add up the length. First, start with the parameter name.
3844 len
= strlen(pm
->pm_name
);
3847 * Scan the parameter value and see if we need to do encoding for this
3851 eightbit
= contains8bit(start
, NULL
);
3854 * Determine if we need to encode this section. Encoding is necessary if:
3856 * - There are any 8-bit characters at all and we're on the first
3858 * - There are 8-bit characters within N bytes of our section start.
3859 * N is calculated based on the number of bytes it would take to
3860 * reach CPERLIN. Specifically:
3861 * 8 (starting tab) +
3862 * strlen(param name) +
3863 * 4 ('* for section marker, '=', opening/closing '"')
3865 * is the number of bytes used by everything that isn't part of the
3866 * value. So that gets subtracted from CPERLIN.
3869 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3870 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3871 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3875 len
++; /* Add in equal sign */
3879 * We're using maxfit as a marker for how many characters we can
3880 * fit into the line. Bump it by two because we're not using quotes
3887 * If we don't have a charset or language tag in this parameter,
3891 if (! pm
->pm_charset
) {
3892 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3893 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3894 die("8-bit characters in parameter \"%s\", but "
3895 "local character set is US-ASCII", pm
->pm_name
);
3898 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3900 len
++; /* For the encoding marker */
3903 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3908 * We know we definitely need to include an index. maxfit already
3909 * includes the section marker.
3911 len
+= strlen(indexchar
);
3913 for (p
= start
; *p
!= '\0'; p
++) {
3914 if (isparamencode(*p
)) {
3922 * Just so there's no confusion: maxfit is counting OUTPUT
3923 * characters (post-encoding). fit is counting INPUT characters.
3925 if (! fitlimit
&& maxfit
>= 0)
3927 else if (! fitlimit
)
3932 * Calculate the string length, but add room for quoting \
3933 * and " if necessary. Also account for quotes at beginning
3936 for (p
= start
; *p
!= '\0'; p
++) {
3947 if (! fitlimit
&& maxfit
>= 0)
3949 else if (! fitlimit
)
3966 * Output an encoded parameter string.
3970 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3971 size_t valueoff
, int index
)
3973 size_t outlen
= 0, n
;
3974 char *endptr
= output
+ len
, *p
;
3977 * First, output the marker for an encoded string.
3985 * If the index is 0, output the character set and language tag.
3986 * If theses were NULL, they should have already been filled in
3991 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3995 if (output
> endptr
) {
3996 inform("Internal error: parameter buffer overflow");
4002 * Copy over the value, encoding if necessary
4005 p
= pm
->pm_value
+ valueoff
;
4006 while (valuelen
-- > 0) {
4007 if (isparamencode(*p
)) {
4008 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4015 if (output
> endptr
) {
4016 inform("Internal error: parameter buffer overflow");
4027 * Output a "normal" parameter, without encoding. Be sure to escape
4028 * quotes and backslashes if necessary.
4032 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4036 char *endptr
= output
+ len
, *p
;
4042 p
= pm
->pm_value
+ valueoff
;
4044 while (valuelen
-- > 0) {
4055 if (output
> endptr
) {
4056 inform("Internal error: parameter buffer overflow");
4061 if (output
- 2 > endptr
) {
4062 inform("Internal error: parameter buffer overflow");
4073 * Add a parameter to the parameter linked list
4077 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4082 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4083 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4086 (*last
)->pm_next
= pm
;
4097 * Either replace a current parameter with a new value, or add the parameter
4098 * to the parameter linked list.
4102 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4106 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4107 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4109 * If nocopy is set, it's assumed that we own both name
4110 * and value. We don't need name, so we discard it now.
4115 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4120 return add_param(first
, last
, name
, value
, nocopy
);
4124 * Retrieve a parameter value from a parameter linked list. If the parameter
4125 * value needs converted to the local character set, do that now.
4129 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4131 while (first
!= NULL
) {
4132 if (strcasecmp(name
, first
->pm_name
) == 0) {
4134 return first
->pm_value
;
4135 return getcpy(get_param_value(first
, replace
));
4137 first
= first
->pm_next
;
4144 * Return a parameter value, converting to the local character set if
4149 get_param_value(PM pm
, char replace
)
4151 static char buffer
[4096]; /* I hope no parameters are larger */
4152 size_t bufsize
= sizeof(buffer
);
4157 ICONV_CONST
char *p
;
4158 #else /* HAVE_ICONV */
4160 #endif /* HAVE_ICONV */
4165 * If we don't have a character set indicated, it's assumed to be
4166 * US-ASCII. If it matches our character set, we don't need to convert
4170 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4171 strlen(pm
->pm_charset
))) {
4172 return pm
->pm_value
;
4176 * In this case, we need to convert. If we have iconv support, use
4177 * that. Otherwise, go through and simply replace every non-ASCII
4178 * character with the substitution character.
4183 bufsize
= sizeof(buffer
);
4184 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4186 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4187 if (cd
== (iconv_t
) -1) {
4191 inbytes
= strlen(pm
->pm_value
);
4195 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4196 if (errno
!= EILSEQ
) {
4201 * Reset shift state, substitute our character,
4202 * try to restart conversion.
4205 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4218 for (++p
, --inbytes
;
4219 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4238 #endif /* HAVE_ICONV */
4241 * Take everything non-ASCII and substitute the replacement character
4245 bufsize
= sizeof(buffer
);
4246 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4247 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))