3 * mhparse.c -- routines to parse the contents of MIME messages
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
16 #include <h/mhparse.h>
20 #endif /* HAVE_ICONV */
26 extern int rcachesw
; /* mhcachesbr.c */
27 extern int wcachesw
; /* mhcachesbr.c */
29 int checksw
= 0; /* check Content-MD5 field */
32 * These are for mhfixmsg to:
33 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
35 * 2) Suppress the warning about bogus multipart content, and report it.
36 * 3) Suppress the warning about extraneous trailing ';' in header parameter
37 * lists, and report it.
39 int skip_mp_cte_check
;
40 int suppress_bogus_mp_content_warning
;
42 int suppress_extraneous_trailing_semicolon_warning
;
43 int extraneous_trailing_semicolon
;
44 int suppress_multiple_mime_version_warning
= 1;
46 /* list of preferred type/subtype pairs, for -prefer */
47 char *preferred_types
[NPREFS
],
48 *preferred_subtypes
[NPREFS
];
53 * Structures for TEXT messages
55 struct k2v SubText
[] = {
56 { "plain", TEXT_PLAIN
},
57 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC-1341 */
58 { "enriched", TEXT_ENRICHED
}, /* defined in RFC-1896 */
59 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
62 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
65 * Structures for MULTIPART messages
67 struct k2v SubMultiPart
[] = {
68 { "mixed", MULTI_MIXED
},
69 { "alternative", MULTI_ALTERNATE
},
70 { "digest", MULTI_DIGEST
},
71 { "parallel", MULTI_PARALLEL
},
72 { "related", MULTI_RELATED
},
73 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
77 * Structures for MESSAGE messages
79 struct k2v SubMessage
[] = {
80 { "rfc822", MESSAGE_RFC822
},
81 { "partial", MESSAGE_PARTIAL
},
82 { "external-body", MESSAGE_EXTERNAL
},
83 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
87 * Structure for APPLICATION messages
89 struct k2v SubApplication
[] = {
90 { "octet-stream", APPLICATION_OCTETS
},
91 { "postscript", APPLICATION_POSTSCRIPT
},
92 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
96 * Mapping of names of CTE types in mhbuild directives
98 static struct k2v EncodingType
[] = {
101 { "q-p", CE_QUOTED
},
102 { "quoted-printable", CE_QUOTED
},
103 { "b64", CE_BASE64
},
104 { "base64", CE_BASE64
},
110 int find_cache (CT
, int, int *, char *, char *, int);
114 int type_ok (CT
, int);
115 void content_error (char *, CT
, char *, ...);
118 void free_encoding (CT
, int);
123 static CT
get_content (FILE *, char *, int);
124 static int get_comment (const char *, const char *, char **, char **);
126 static int InitGeneric (CT
);
127 static int InitText (CT
);
128 static int InitMultiPart (CT
);
129 static void reverse_parts (CT
);
130 static void prefer_parts(CT ct
);
131 static int InitMessage (CT
);
132 static int InitApplication (CT
);
133 static int init_encoding (CT
, OpenCEFunc
);
134 static unsigned long size_encoding (CT
);
135 static int InitBase64 (CT
);
136 static int openBase64 (CT
, char **);
137 static int InitQuoted (CT
);
138 static int openQuoted (CT
, char **);
139 static int Init7Bit (CT
);
140 static int openExternal (CT
, CT
, CE
, char **, int *);
141 static int InitFile (CT
);
142 static int openFile (CT
, char **);
143 static int InitFTP (CT
);
144 static int openFTP (CT
, char **);
145 static int InitMail (CT
);
146 static int openMail (CT
, char **);
147 static int readDigest (CT
, char *);
148 static int get_leftover_mp_content (CT
, int);
149 static int InitURL (CT
);
150 static int openURL (CT
, char **);
151 static int parse_header_attrs (const char *, const char *, char **, PM
*,
153 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
154 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
155 static int get_dispo (char *, CT
, int);
157 struct str2init str2cts
[] = {
158 { "application", CT_APPLICATION
, InitApplication
},
159 { "audio", CT_AUDIO
, InitGeneric
},
160 { "image", CT_IMAGE
, InitGeneric
},
161 { "message", CT_MESSAGE
, InitMessage
},
162 { "multipart", CT_MULTIPART
, InitMultiPart
},
163 { "text", CT_TEXT
, InitText
},
164 { "video", CT_VIDEO
, InitGeneric
},
165 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
166 { NULL
, CT_UNKNOWN
, NULL
},
169 struct str2init str2ces
[] = {
170 { "base64", CE_BASE64
, InitBase64
},
171 { "quoted-printable", CE_QUOTED
, InitQuoted
},
172 { "8bit", CE_8BIT
, Init7Bit
},
173 { "7bit", CE_7BIT
, Init7Bit
},
174 { "binary", CE_BINARY
, Init7Bit
},
175 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
176 { NULL
, CE_UNKNOWN
, NULL
},
180 * NOTE WELL: si_key MUST NOT have value of NOTOK
182 * si_key is 1 if access method is anonymous.
184 struct str2init str2methods
[] = {
185 { "afs", 1, InitFile
},
186 { "anon-ftp", 1, InitFTP
},
187 { "ftp", 0, InitFTP
},
188 { "local-file", 0, InitFile
},
189 { "mail-server", 0, InitMail
},
190 { "url", 0, InitURL
},
196 * Main entry point for parsing a MIME message or file.
197 * It returns the Content structure for the top level
198 * entity in the file.
202 parse_mime (char *file
)
211 * Check if file is actually standard input
213 if ((is_stdin
= !(strcmp (file
, "-")))) {
214 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
216 advise("mhparse", "unable to create temporary file in %s",
220 file
= add (tfile
, NULL
);
222 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
223 if (fwrite(buffer
, 1, n
, fp
) != n
) {
224 (void) m_unlink (file
);
225 advise (file
, "error copying to temporary file");
231 if (ferror (stdin
)) {
232 (void) m_unlink (file
);
233 advise ("stdin", "error reading");
237 (void) m_unlink (file
);
238 advise (file
, "error writing");
241 fseek (fp
, 0L, SEEK_SET
);
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 advise (NULL
, "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
[BUFSIZ
], name
[NAMESZ
];
294 m_getfld_state_t gstate
= 0;
296 /* allocate the content structure */
299 ct
->c_file
= add (file
, NULL
);
300 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
303 * Parse the header fields for this
304 * content into a linked list.
306 m_getfld_track_filepos (&gstate
, in
);
307 for (compnum
= 1;;) {
308 int bufsz
= sizeof buf
;
309 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
314 /* get copies of the buffers */
315 np
= add (name
, NULL
);
316 vp
= add (buf
, NULL
);
318 /* if necessary, get rest of field */
319 while (state
== FLDPLUS
) {
321 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
322 vp
= add (buf
, vp
); /* add to previous value */
325 /* Now add the header data to the list */
326 add_header (ct
, np
, vp
);
328 /* continue, to see if this isn't the last header field */
329 ct
->c_begin
= ftell (in
) + 1;
333 ct
->c_begin
= ftell (in
) - strlen (buf
);
337 ct
->c_begin
= ftell (in
);
342 adios (NULL
, "message format error in component #%d", compnum
);
345 adios (NULL
, "getfld() returned %d", state
);
348 /* break out of the loop */
351 m_getfld_state_destroy (&gstate
);
354 * Read the content headers. We will parse the
355 * MIME related header fields into their various
356 * structures and set internal flags related to
357 * content type/subtype, etc.
360 hp
= ct
->c_first_hf
; /* start at first header field */
362 /* Get MIME-Version field */
363 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
368 vrsn
= add (hp
->value
, NULL
);
370 /* Now, cleanup this field */
373 while (isspace ((unsigned char) *cp
))
375 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
377 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
378 if (!isspace ((unsigned char) *dp
))
382 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
385 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
388 for (dp
= cp
; istoken (*dp
); dp
++)
392 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
395 admonish (NULL
, "message %s has unknown value for %s: field (%s)",
396 ct
->c_file
, VRSN_FIELD
, cp
);
401 if (! suppress_multiple_mime_version_warning
)
402 advise (NULL
, "message %s has multiple %s: fields",
403 ct
->c_file
, VRSN_FIELD
);
407 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
408 /* Get Content-Type field */
409 struct str2init
*s2i
;
410 CI ci
= &ct
->c_ctinfo
;
412 /* Check if we've already seen a Content-Type header */
414 advise (NULL
, "message %s has multiple %s: fields",
415 ct
->c_file
, TYPE_FIELD
);
419 /* Parse the Content-Type field */
420 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
424 * Set the Init function and the internal
425 * flag for this content type.
427 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
428 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
430 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
432 ct
->c_type
= s2i
->si_val
;
433 ct
->c_ctinitfnx
= s2i
->si_init
;
435 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
436 /* Get Content-Transfer-Encoding field */
438 struct str2init
*s2i
;
441 * Check if we've already seen the
442 * Content-Transfer-Encoding field
445 advise (NULL
, "message %s has multiple %s: fields",
446 ct
->c_file
, ENCODING_FIELD
);
450 /* get copy of this field */
451 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
453 while (isspace ((unsigned char) *cp
))
455 for (dp
= cp
; istoken (*dp
); dp
++)
461 * Find the internal flag and Init function
462 * for this transfer encoding.
464 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
465 if (!strcasecmp (cp
, s2i
->si_key
))
467 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
470 ct
->c_encoding
= s2i
->si_val
;
472 /* Call the Init function for this encoding */
473 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
476 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
477 /* Get Content-MD5 field */
483 if (ct
->c_digested
) {
484 advise (NULL
, "message %s has multiple %s: fields",
485 ct
->c_file
, MD5_FIELD
);
489 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
491 while (isspace ((unsigned char) *cp
))
493 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
495 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
496 if (!isspace ((unsigned char) *dp
))
500 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
503 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
508 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
516 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
517 /* Get Content-ID field */
518 ct
->c_id
= add (hp
->value
, ct
->c_id
);
520 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
521 /* Get Content-Description field */
522 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
524 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
525 /* Get Content-Disposition field */
526 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
531 hp
= hp
->next
; /* next header field */
535 * Check if we saw a Content-Type field.
536 * If not, then assign a default value for
537 * it, and the Init function.
541 * If we are inside a multipart/digest message,
542 * so default type is message/rfc822
545 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
547 ct
->c_type
= CT_MESSAGE
;
548 ct
->c_ctinitfnx
= InitMessage
;
551 * Else default type is text/plain
553 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
555 ct
->c_type
= CT_TEXT
;
556 ct
->c_ctinitfnx
= InitText
;
560 /* Use default Transfer-Encoding, if necessary */
562 ct
->c_encoding
= CE_7BIT
;
575 * small routine to add header field to list
579 add_header (CT ct
, char *name
, char *value
)
583 /* allocate header field structure */
586 /* link data into header structure */
591 /* link header structure into the list */
592 if (ct
->c_first_hf
== NULL
) {
593 ct
->c_first_hf
= hp
; /* this is the first */
596 ct
->c_last_hf
->next
= hp
; /* add it to the end */
605 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
606 * directives. Fills in the information of the CTinfo structure.
609 get_ctinfo (char *cp
, CT ct
, int magic
)
618 /* store copy of Content-Type line */
619 cp
= ct
->c_ctline
= add (cp
, NULL
);
621 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
624 /* change newlines to spaces */
625 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
628 /* trim trailing spaces */
629 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
630 if (!isspace ((unsigned char) *dp
))
635 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
637 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
638 &ci
->ci_comment
) == NOTOK
)
641 for (dp
= cp
; istoken (*dp
); dp
++)
644 ci
->ci_type
= add (cp
, NULL
); /* store content type */
648 advise (NULL
, "invalid %s: field in message %s (empty type)",
649 TYPE_FIELD
, ct
->c_file
);
653 /* down case the content type string */
654 for (dp
= ci
->ci_type
; *dp
; dp
++)
655 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
656 *dp
= tolower ((unsigned char) *dp
);
658 while (isspace ((unsigned char) *cp
))
661 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
662 &ci
->ci_comment
) == NOTOK
)
667 ci
->ci_subtype
= add ("", NULL
);
672 while (isspace ((unsigned char) *cp
))
675 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
676 &ci
->ci_comment
) == NOTOK
)
679 for (dp
= cp
; istoken (*dp
); dp
++)
682 ci
->ci_subtype
= add (cp
, NULL
); /* store the content subtype */
685 if (!*ci
->ci_subtype
) {
687 "invalid %s: field in message %s (empty subtype for \"%s\")",
688 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
692 /* down case the content subtype string */
693 for (dp
= ci
->ci_subtype
; *dp
; dp
++)
694 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
695 *dp
= tolower ((unsigned char) *dp
);
698 while (isspace ((unsigned char) *cp
))
701 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
702 &ci
->ci_comment
) == NOTOK
)
705 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
706 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
707 &ci
->ci_comment
)) != OK
) {
708 return status
== NOTOK
? NOTOK
: OK
;
712 * Get any <Content-Id> given in buffer
714 if (magic
&& *cp
== '<') {
719 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
720 advise (NULL
, "invalid ID in message %s", ct
->c_file
);
726 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
732 while (isspace ((unsigned char) *cp
))
737 * Get any [Content-Description] given in buffer.
739 if (magic
&& *cp
== '[') {
741 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
745 advise (NULL
, "invalid description in message %s", ct
->c_file
);
753 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
759 while (isspace ((unsigned char) *cp
))
764 * Get any {Content-Disposition} given in buffer.
766 if (magic
&& *cp
== '{') {
768 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
772 advise (NULL
, "invalid disposition in message %s", ct
->c_file
);
780 if (get_dispo(cp
, ct
, 1) != OK
)
786 while (isspace ((unsigned char) *cp
))
791 * Get any extension directives (right now just the content transfer
792 * encoding, but maybe others) that we care about.
795 if (magic
&& *cp
== '*') {
797 * See if it's a CTE we match on
802 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
806 advise (NULL
, "invalid null transfer encoding specification");
813 ct
->c_reqencoding
= CE_UNKNOWN
;
815 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
816 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
817 ct
->c_reqencoding
= kv
->kv_value
;
822 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
823 advise (NULL
, "invalid CTE specification: \"%s\"", dp
);
827 while (isspace ((unsigned char) *cp
))
832 * Check if anything is left over
836 ci
->ci_magic
= add (cp
, NULL
);
838 /* If there is a Content-Disposition header and it doesn't
839 have a *filename=, extract it from the magic contents.
840 The r1bindex call skips any leading directory
842 if (ct
->c_dispo_type
&&
843 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
844 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
845 r1bindex(ci
->ci_magic
, '/'), 0);
850 "extraneous information in message %s's %s: field\n%*s(%s)",
851 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
859 * Parse out a Content-Disposition header. A lot of this is cribbed from
863 get_dispo (char *cp
, CT ct
, int buildflag
)
865 char *dp
, *dispoheader
;
870 * Save the whole copy of the Content-Disposition header, unless we're
871 * processing a mhbuild directive. A NULL c_dispo will be a flag to
872 * mhbuild that the disposition header needs to be generated at that
876 dispoheader
= cp
= add(cp
, NULL
);
878 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
881 /* change newlines to spaces */
882 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
885 /* trim trailing spaces */
886 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
887 if (!isspace ((unsigned char) *dp
))
892 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
894 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
900 for (dp
= cp
; istoken (*dp
); dp
++)
903 ct
->c_dispo_type
= add (cp
, NULL
); /* store disposition type */
906 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
909 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
910 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
912 if (status
== NOTOK
) {
918 "extraneous information in message %s's %s: field\n%*s(%s)",
919 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
925 ct
->c_dispo
= dispoheader
;
932 get_comment (const char *filename
, const char *fieldname
, char **ap
,
937 char c
, buffer
[BUFSIZ
], *dp
;
947 advise (NULL
, "invalid comment in message %s's %s: field",
948 filename
, fieldname
);
953 if ((c
= *cp
++) == '\0')
976 if ((dp
= *commentp
)) {
977 *commentp
= concat (dp
, " ", buffer
, NULL
);
980 *commentp
= add (buffer
, NULL
);
984 while (isspace ((unsigned char) *cp
))
995 * Handles content types audio, image, and video.
996 * There's not much to do right here.
1004 return OK
; /* not much to do here */
1015 char buffer
[BUFSIZ
];
1020 CI ci
= &ct
->c_ctinfo
;
1022 /* check for missing subtype */
1023 if (!*ci
->ci_subtype
)
1024 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1027 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1029 /* allocate text character set structure */
1031 ct
->c_ctparams
= (void *) t
;
1033 /* scan for charset parameter */
1034 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1035 if (!strcasecmp (pm
->pm_name
, "charset"))
1038 /* check if content specified a character set */
1040 chset
= pm
->pm_value
;
1041 t
->tx_charset
= CHARSET_SPECIFIED
;
1043 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1047 * If we can not handle character set natively,
1048 * then check profile for string to modify the
1049 * terminal or display method.
1051 * termproc is for mhshow, though mhlist -debug prints it, too.
1053 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1054 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1055 if ((cp
= context_find (buffer
)))
1056 ct
->c_termproc
= mh_xstrdup(cp
);
1068 InitMultiPart (CT ct
)
1078 struct multipart
*m
;
1079 struct part
*part
, **next
;
1080 CI ci
= &ct
->c_ctinfo
;
1085 * The encoding for multipart messages must be either
1086 * 7bit, 8bit, or binary (per RFC2045).
1088 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1089 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1090 /* Copy the Content-Transfer-Encoding header field body so we can
1091 remove any trailing whitespace and leading blanks from it. */
1092 char *cte
= add (ct
->c_celine
? ct
->c_celine
: "(null)", NULL
);
1094 bp
= cte
+ strlen (cte
) - 1;
1095 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1096 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1099 "\"%s/%s\" type in message %s must be encoded in\n"
1100 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1101 "mhfixmsg -fixcte can fix it, or\n"
1102 "manually edit the file and change the \"%s\"\n"
1103 "Content-Transfer-Encoding to one of those. For now",
1104 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1111 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1114 * Check for "boundary" parameter, which is
1115 * required for multipart messages.
1118 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1119 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1125 /* complain if boundary parameter is missing */
1128 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1129 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1133 /* allocate primary structure for multipart info */
1135 ct
->c_ctparams
= (void *) m
;
1137 /* check if boundary parameter contains only whitespace characters */
1138 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1141 advise (NULL
, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1142 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1146 /* remove trailing whitespace from boundary parameter */
1147 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1148 if (!isspace ((unsigned char) *dp
))
1152 /* record boundary separators */
1153 m
->mp_start
= concat (bp
, "\n", NULL
);
1154 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1156 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1157 advise (ct
->c_file
, "unable to open for reading");
1161 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1163 next
= &m
->mp_parts
;
1167 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1172 if (bufp
[0] != '-' || bufp
[1] != '-')
1175 if (strcmp (bufp
+ 2, m
->mp_start
))
1180 next
= &part
->mp_next
;
1182 if (!(p
= get_content (fp
, ct
->c_file
,
1183 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1191 fseek (fp
, pos
, SEEK_SET
);
1194 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1198 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1199 if (p
->c_end
< p
->c_begin
)
1200 p
->c_begin
= p
->c_end
;
1205 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1210 if (! suppress_bogus_mp_content_warning
) {
1211 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1213 bogus_mp_content
= 1;
1215 if (!inout
&& part
) {
1217 p
->c_end
= ct
->c_end
;
1219 if (p
->c_begin
>= p
->c_end
) {
1220 for (next
= &m
->mp_parts
; *next
!= part
;
1221 next
= &((*next
)->mp_next
))
1225 free ((char *) part
);
1230 /* reverse the order of the parts for multipart/alternative */
1231 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1237 * label all subparts with part number, and
1238 * then initialize the content of the subpart.
1243 char partnam
[BUFSIZ
];
1246 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1247 pp
= partnam
+ strlen (partnam
);
1252 for (part
= m
->mp_parts
, partnum
= 1; part
;
1253 part
= part
->mp_next
, partnum
++) {
1256 sprintf (pp
, "%d", partnum
);
1257 p
->c_partno
= add (partnam
, NULL
);
1259 /* initialize the content of the subparts */
1260 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1269 get_leftover_mp_content (ct
, 1);
1270 get_leftover_mp_content (ct
, 0);
1280 * reverse the order of the parts of a multipart/alternative,
1281 * presumably to put the "most favored" alternative first, for
1282 * ease of choosing/displaying it later on. from a mail message on
1283 * nmh-workers, from kenh:
1284 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1285 * see code in mhn that did the same thing... Acccording to the RCS
1286 * logs, that code was around from the initial checkin of mhn.c by
1287 * John Romine in 1992, which is as far back as we have."
1290 reverse_parts (CT ct
)
1292 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1296 /* Reverse the order of its parts by walking the mp_parts list
1297 and pushing each node to the front. */
1298 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1299 next
= part
->mp_next
;
1300 part
->mp_next
= m
->mp_parts
;
1306 move_preferred_part (CT ct
, char *type
, char *subtype
)
1308 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1309 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1313 /* move the matching part(s) to the head of the list: walk the
1314 * list of parts, move matching parts to a new list (maintaining
1315 * their order), and finally, concatenate the old list onto the
1322 head
->mp_next
= m
->mp_parts
;
1323 nhead
->mp_next
= NULL
;
1327 part
= head
->mp_next
;
1328 while (part
!= NULL
) {
1329 ci
= &part
->mp_part
->c_ctinfo
;
1330 if (!strcasecmp(ci
->ci_type
, type
) &&
1331 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1332 prev
->mp_next
= part
->mp_next
;
1333 part
->mp_next
= NULL
;
1334 ntail
->mp_next
= part
;
1336 part
= prev
->mp_next
;
1339 part
= prev
->mp_next
;
1342 ntail
->mp_next
= head
->mp_next
;
1343 m
->mp_parts
= nhead
->mp_next
;
1348 * move parts that match the user's preferences (-prefer) to the head
1349 * of the line. process preferences in reverse so first one given
1350 * ends up first in line
1356 for (i
= npreferred
-1; i
>= 0; i
--)
1357 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1362 /* parse_mime() arranges alternates in reverse (priority) order. This
1363 function can be used to reverse them back. This will put, for
1364 example, a text/plain part before a text/html part in a
1365 multipart/alternative part, for example, where it belongs. */
1367 reverse_alternative_parts (CT ct
) {
1368 if (ct
->c_type
== CT_MULTIPART
) {
1369 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1372 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1376 /* And call recursively on each part of a multipart. */
1377 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1378 reverse_alternative_parts (part
->mp_part
);
1391 CI ci
= &ct
->c_ctinfo
;
1393 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1395 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1396 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1400 /* check for missing subtype */
1401 if (!*ci
->ci_subtype
)
1402 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1405 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1407 switch (ct
->c_subtype
) {
1408 case MESSAGE_RFC822
:
1411 case MESSAGE_PARTIAL
:
1417 ct
->c_ctparams
= (void *) p
;
1419 /* scan for parameters "id", "number", and "total" */
1420 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1421 if (!strcasecmp (pm
->pm_name
, "id")) {
1422 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1425 if (!strcasecmp (pm
->pm_name
, "number")) {
1426 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1427 || p
->pm_partno
< 1) {
1430 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1431 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1432 ct
->c_file
, TYPE_FIELD
);
1437 if (!strcasecmp (pm
->pm_name
, "total")) {
1438 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1447 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1449 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1450 ci
->ci_type
, ci
->ci_subtype
,
1451 ct
->c_file
, TYPE_FIELD
);
1457 case MESSAGE_EXTERNAL
:
1465 ct
->c_ctparams
= (void *) e
;
1468 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1469 advise (ct
->c_file
, "unable to open for reading");
1473 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1475 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1483 p
->c_ceopenfnx
= NULL
;
1484 if ((exresult
= params_external (ct
, 0)) != NOTOK
1485 && p
->c_ceopenfnx
== openMail
) {
1489 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1491 content_error (NULL
, ct
,
1492 "empty body for access-type=mail-server");
1496 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1497 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1499 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1501 adios ("failed", "fread");
1504 adios (NULL
, "unexpected EOF from fread");
1507 bp
+= cc
, size
-= cc
;
1514 p
->c_end
= p
->c_begin
;
1519 if (exresult
== NOTOK
)
1521 if (e
->eb_flags
== NOTOK
)
1524 switch (p
->c_type
) {
1529 if (p
->c_subtype
!= MESSAGE_RFC822
)
1533 e
->eb_partno
= ct
->c_partno
;
1535 (*p
->c_ctinitfnx
) (p
);
1550 params_external (CT ct
, int composing
)
1553 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1554 CI ci
= &ct
->c_ctinfo
;
1556 ct
->c_ceopenfnx
= NULL
;
1557 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1558 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1559 struct str2init
*s2i
;
1560 CT p
= e
->eb_content
;
1562 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1563 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1566 e
->eb_access
= pm
->pm_value
;
1567 e
->eb_flags
= NOTOK
;
1568 p
->c_encoding
= CE_EXTERNAL
;
1571 e
->eb_access
= s2i
->si_key
;
1572 e
->eb_flags
= s2i
->si_val
;
1573 p
->c_encoding
= CE_EXTERNAL
;
1575 /* Call the Init function for this external type */
1576 if ((*s2i
->si_init
)(p
) == NOTOK
)
1580 if (!strcasecmp (pm
->pm_name
, "name")) {
1581 e
->eb_name
= pm
->pm_value
;
1584 if (!strcasecmp (pm
->pm_name
, "permission")) {
1585 e
->eb_permission
= pm
->pm_value
;
1588 if (!strcasecmp (pm
->pm_name
, "site")) {
1589 e
->eb_site
= pm
->pm_value
;
1592 if (!strcasecmp (pm
->pm_name
, "directory")) {
1593 e
->eb_dir
= pm
->pm_value
;
1596 if (!strcasecmp (pm
->pm_name
, "mode")) {
1597 e
->eb_mode
= pm
->pm_value
;
1600 if (!strcasecmp (pm
->pm_name
, "size")) {
1601 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1604 if (!strcasecmp (pm
->pm_name
, "server")) {
1605 e
->eb_server
= pm
->pm_value
;
1608 if (!strcasecmp (pm
->pm_name
, "subject")) {
1609 e
->eb_subject
= pm
->pm_value
;
1612 if (!strcasecmp (pm
->pm_name
, "url")) {
1614 * According to RFC 2017, we have to remove all whitespace from
1618 char *u
, *p
= pm
->pm_value
;
1619 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1621 for (; *p
!= '\0'; p
++) {
1622 if (! isspace((unsigned char) *p
))
1629 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1630 e
->eb_body
= getcpy (pm
->pm_value
);
1635 if (!e
->eb_access
) {
1637 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1638 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1651 InitApplication (CT ct
)
1653 CI ci
= &ct
->c_ctinfo
;
1656 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1663 * TRANSFER ENCODINGS
1667 init_encoding (CT ct
, OpenCEFunc openfnx
)
1669 ct
->c_ceopenfnx
= openfnx
;
1670 ct
->c_ceclosefnx
= close_encoding
;
1671 ct
->c_cesizefnx
= size_encoding
;
1678 close_encoding (CT ct
)
1680 CE ce
= &ct
->c_cefile
;
1689 static unsigned long
1690 size_encoding (CT ct
)
1695 CE ce
= &ct
->c_cefile
;
1698 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1699 return (long) st
.st_size
;
1702 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1703 return (long) st
.st_size
;
1707 if (ct
->c_encoding
== CE_EXTERNAL
)
1708 return (ct
->c_end
- ct
->c_begin
);
1711 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1712 return (ct
->c_end
- ct
->c_begin
);
1714 if (fstat (fd
, &st
) != NOTOK
)
1715 size
= (long) st
.st_size
;
1719 (*ct
->c_ceclosefnx
) (ct
);
1731 return init_encoding (ct
, openBase64
);
1736 openBase64 (CT ct
, char **file
)
1739 int fd
, own_ct_fp
= 0;
1740 char *cp
, *buffer
= NULL
;
1741 /* sbeck -- handle suffixes */
1743 CE ce
= &ct
->c_cefile
;
1744 unsigned char *decoded
;
1746 unsigned char digest
[16];
1749 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1754 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1755 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1761 if (*file
== NULL
) {
1764 ce
->ce_file
= add (*file
, NULL
);
1768 /* sbeck@cise.ufl.edu -- handle suffixes */
1770 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1771 if (ce
->ce_unlink
) {
1772 /* Create temporary file with filename extension. */
1773 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1774 adios(NULL
, "unable to create temporary file in %s",
1778 ce
->ce_file
= add (cp
, ce
->ce_file
);
1780 } else if (*file
== NULL
) {
1782 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1783 adios(NULL
, "unable to create temporary file in %s",
1786 ce
->ce_file
= add (tempfile
, NULL
);
1789 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1790 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1794 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1795 adios (NULL
, "internal error(1)");
1797 buffer
= mh_xmalloc (len
+ 1);
1800 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1801 content_error (ct
->c_file
, ct
, "unable to open for reading");
1807 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1810 switch (cc
= read (fd
, cp
, len
)) {
1812 content_error (ct
->c_file
, ct
, "error reading from");
1816 content_error (NULL
, ct
, "premature eof");
1827 /* decodeBase64() requires null-terminated input. */
1830 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1831 ct
->c_digested
? digest
: NULL
) == OK
) {
1833 unsigned char *decoded_p
= decoded
;
1834 for (i
= 0; i
< decoded_len
; ++i
) {
1835 putc (*decoded_p
++, ce
->ce_fp
);
1837 free ((char *) decoded
);
1838 if (ferror (ce
->ce_fp
)) {
1839 content_error (ce
->ce_file
, ct
, "error writing to");
1843 if (ct
->c_digested
) {
1844 if (memcmp(digest
, ct
->c_digest
,
1845 sizeof(digest
) / sizeof(digest
[0]))) {
1846 content_error (NULL
, ct
,
1847 "content integrity suspect (digest mismatch) -- continuing");
1850 fprintf (stderr
, "content integrity confirmed\n");
1858 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1860 if (fflush (ce
->ce_fp
)) {
1861 content_error (ce
->ce_file
, ct
, "error writing to");
1865 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1868 *file
= ce
->ce_file
;
1874 return fileno (ce
->ce_fp
);
1881 free_encoding (ct
, 0);
1891 static char hex2nib
[0x80] = {
1892 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1893 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1894 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1895 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1896 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1897 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1898 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1899 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1900 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1901 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 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
1914 return init_encoding (ct
, openQuoted
);
1919 openQuoted (CT ct
, char **file
)
1921 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1927 CE ce
= &ct
->c_cefile
;
1928 /* sbeck -- handle suffixes */
1933 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1938 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1939 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1945 if (*file
== NULL
) {
1948 ce
->ce_file
= add (*file
, NULL
);
1952 /* sbeck@cise.ufl.edu -- handle suffixes */
1954 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1955 if (ce
->ce_unlink
) {
1956 /* Create temporary file with filename extension. */
1957 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1958 adios(NULL
, "unable to create temporary file in %s",
1962 ce
->ce_file
= add (cp
, ce
->ce_file
);
1964 } else if (*file
== NULL
) {
1966 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1967 adios(NULL
, "unable to create temporary file in %s",
1970 ce
->ce_file
= add (tempfile
, NULL
);
1973 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1974 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1978 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1979 adios (NULL
, "internal error(2)");
1982 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1983 content_error (ct
->c_file
, ct
, "unable to open for reading");
1989 if ((digested
= ct
->c_digested
))
1990 MD5Init (&mdContext
);
1997 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1999 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2000 content_error (NULL
, ct
, "premature eof");
2004 if ((cc
= gotlen
) > len
)
2008 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2009 if (!isspace ((unsigned char) *ep
))
2013 for (; cp
< ep
; cp
++) {
2015 /* in an escape sequence */
2017 /* at byte 1 of an escape sequence */
2018 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2019 /* next is byte 2 */
2022 /* at byte 2 of an escape sequence */
2024 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2025 putc (mask
, ce
->ce_fp
);
2027 MD5Update (&mdContext
, &mask
, 1);
2028 if (ferror (ce
->ce_fp
)) {
2029 content_error (ce
->ce_file
, ct
, "error writing to");
2032 /* finished escape sequence; next may be literal or a new
2033 * escape sequence */
2036 /* on to next byte */
2040 /* not in an escape sequence */
2042 /* starting an escape sequence, or invalid '='? */
2043 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2044 /* "=\n" soft line break, eat the \n */
2048 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2049 /* We don't have 2 bytes left, so this is an invalid
2050 * escape sequence; just show the raw bytes (below). */
2051 } else if (isxdigit ((unsigned char) cp
[1]) &&
2052 isxdigit ((unsigned char) cp
[2])) {
2053 /* Next 2 bytes are hex digits, making this a valid escape
2054 * sequence; let's decode it (above). */
2058 /* One or both of the next 2 is out of range, making this
2059 * an invalid escape sequence; just show the raw bytes
2064 /* Just show the raw byte. */
2065 putc (*cp
, ce
->ce_fp
);
2068 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2070 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2073 if (ferror (ce
->ce_fp
)) {
2074 content_error (ce
->ce_file
, ct
, "error writing to");
2080 content_error (NULL
, ct
,
2081 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2085 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2087 if (fflush (ce
->ce_fp
)) {
2088 content_error (ce
->ce_file
, ct
, "error writing to");
2093 unsigned char digest
[16];
2095 MD5Final (digest
, &mdContext
);
2096 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2097 sizeof(digest
) / sizeof(digest
[0])))
2098 content_error (NULL
, ct
,
2099 "content integrity suspect (digest mismatch) -- continuing");
2102 fprintf (stderr
, "content integrity confirmed\n");
2105 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2108 *file
= ce
->ce_file
;
2114 return fileno (ce
->ce_fp
);
2117 free_encoding (ct
, 0);
2134 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2137 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2143 open7Bit (CT ct
, char **file
)
2145 int cc
, fd
, len
, own_ct_fp
= 0;
2146 char buffer
[BUFSIZ
];
2147 /* sbeck -- handle suffixes */
2150 CE ce
= &ct
->c_cefile
;
2153 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2158 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2159 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2165 if (*file
== NULL
) {
2168 ce
->ce_file
= add (*file
, NULL
);
2172 /* sbeck@cise.ufl.edu -- handle suffixes */
2174 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2175 if (ce
->ce_unlink
) {
2176 /* Create temporary file with filename extension. */
2177 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2178 adios(NULL
, "unable to create temporary file in %s",
2182 ce
->ce_file
= add (cp
, ce
->ce_file
);
2184 } else if (*file
== NULL
) {
2186 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2187 adios(NULL
, "unable to create temporary file in %s",
2190 ce
->ce_file
= add (tempfile
, NULL
);
2193 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2194 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2198 if (ct
->c_type
== CT_MULTIPART
) {
2199 CI ci
= &ct
->c_ctinfo
;
2203 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2204 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2205 + 1 + strlen (ci
->ci_subtype
);
2206 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2209 fputs (buffer
, ce
->ce_fp
);
2213 if (ci
->ci_comment
) {
2214 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2215 fputs ("\n\t", ce
->ce_fp
);
2219 putc (' ', ce
->ce_fp
);
2222 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2225 fprintf (ce
->ce_fp
, "\n");
2227 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2229 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2231 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2232 fprintf (ce
->ce_fp
, "\n");
2235 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2236 adios (NULL
, "internal error(3)");
2239 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2240 content_error (ct
->c_file
, ct
, "unable to open for reading");
2246 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2248 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2250 content_error (ct
->c_file
, ct
, "error reading from");
2254 content_error (NULL
, ct
, "premature eof");
2262 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2263 advise ("open7Bit", "fwrite");
2265 if (ferror (ce
->ce_fp
)) {
2266 content_error (ce
->ce_file
, ct
, "error writing to");
2271 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2273 if (fflush (ce
->ce_fp
)) {
2274 content_error (ce
->ce_file
, ct
, "error writing to");
2278 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2281 *file
= ce
->ce_file
;
2286 return fileno (ce
->ce_fp
);
2289 free_encoding (ct
, 0);
2303 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2305 char cachefile
[BUFSIZ
];
2308 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2313 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2314 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2320 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2321 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2322 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2323 ce
->ce_file
= mh_xstrdup(cachefile
);
2327 admonish (cachefile
, "unable to fopen for reading");
2330 *fd
= fileno (ce
->ce_fp
);
2334 *file
= ce
->ce_file
;
2335 *fd
= fileno (ce
->ce_fp
);
2346 return init_encoding (ct
, openFile
);
2351 openFile (CT ct
, char **file
)
2354 char cachefile
[BUFSIZ
];
2355 struct exbody
*e
= ct
->c_ctexbody
;
2356 CE ce
= &ct
->c_cefile
;
2358 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2370 content_error (NULL
, ct
, "missing name parameter");
2374 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2377 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2378 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2382 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2383 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2384 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2388 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2389 if ((fp
= fopen (cachefile
, "w"))) {
2391 char buffer
[BUFSIZ
];
2392 FILE *gp
= ce
->ce_fp
;
2394 fseek (gp
, 0L, SEEK_SET
);
2396 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2398 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2399 advise ("openFile", "fwrite");
2404 admonish (ce
->ce_file
, "error reading");
2405 (void) m_unlink (cachefile
);
2409 admonish (cachefile
, "error writing");
2410 (void) m_unlink (cachefile
);
2417 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2418 *file
= ce
->ce_file
;
2419 return fileno (ce
->ce_fp
);
2429 return init_encoding (ct
, openFTP
);
2434 openFTP (CT ct
, char **file
)
2436 int cachetype
, caching
, fd
;
2438 char *bp
, *ftp
, *user
, *pass
;
2439 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2441 CE ce
= &ct
->c_cefile
;
2442 static char *username
= NULL
;
2443 static char *password
= NULL
;
2447 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2453 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2464 if (!e
->eb_name
|| !e
->eb_site
) {
2465 content_error (NULL
, ct
, "missing %s parameter",
2466 e
->eb_name
? "site": "name");
2470 /* Get the buffer ready to go */
2472 buflen
= sizeof(buffer
);
2475 * Construct the query message for user
2477 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2483 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2489 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2490 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2495 if (e
->eb_size
> 0) {
2496 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2501 snprintf (bp
, buflen
, "? ");
2504 * Now, check the answer
2506 if (!read_yes_or_no_if_tty (buffer
))
2511 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2515 ruserpass (e
->eb_site
, &username
, &password
, 0);
2520 ce
->ce_unlink
= (*file
== NULL
);
2522 cachefile
[0] = '\0';
2523 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2524 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2525 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2526 if (*file
== NULL
) {
2533 ce
->ce_file
= add (*file
, NULL
);
2535 ce
->ce_file
= add (cachefile
, NULL
);
2538 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2539 adios(NULL
, "unable to create temporary file in %s",
2542 ce
->ce_file
= add (tempfile
, NULL
);
2545 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2546 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2551 int child_id
, i
, vecp
;
2555 vec
[vecp
++] = r1bindex (ftp
, '/');
2556 vec
[vecp
++] = e
->eb_site
;
2559 vec
[vecp
++] = e
->eb_dir
;
2560 vec
[vecp
++] = e
->eb_name
;
2561 vec
[vecp
++] = ce
->ce_file
,
2562 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2563 ? "ascii" : "binary";
2568 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2572 adios ("fork", "unable to");
2576 close (fileno (ce
->ce_fp
));
2578 fprintf (stderr
, "unable to exec ");
2584 if (pidXwait (child_id
, NULL
)) {
2585 username
= password
= NULL
;
2595 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2600 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2601 if ((fp
= fopen (cachefile
, "w"))) {
2603 FILE *gp
= ce
->ce_fp
;
2605 fseek (gp
, 0L, SEEK_SET
);
2607 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2609 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2610 advise ("openFTP", "fwrite");
2615 admonish (ce
->ce_file
, "error reading");
2616 (void) m_unlink (cachefile
);
2620 admonish (cachefile
, "error writing");
2621 (void) m_unlink (cachefile
);
2629 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2630 *file
= ce
->ce_file
;
2631 return fileno (ce
->ce_fp
);
2642 return init_encoding (ct
, openMail
);
2647 openMail (CT ct
, char **file
)
2649 int child_id
, fd
, i
, vecp
;
2651 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2652 struct exbody
*e
= ct
->c_ctexbody
;
2653 CE ce
= &ct
->c_cefile
;
2655 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2666 if (!e
->eb_server
) {
2667 content_error (NULL
, ct
, "missing server parameter");
2671 /* Get buffer ready to go */
2673 buflen
= sizeof(buffer
);
2675 /* Now, construct query message */
2676 snprintf (bp
, buflen
, "Retrieve content");
2682 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2688 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2690 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2692 /* Now, check answer */
2693 if (!read_yes_or_no_if_tty (buffer
))
2697 vec
[vecp
++] = r1bindex (mailproc
, '/');
2698 vec
[vecp
++] = e
->eb_server
;
2699 vec
[vecp
++] = "-subject";
2700 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2701 vec
[vecp
++] = "-body";
2702 vec
[vecp
++] = e
->eb_body
;
2705 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2709 advise ("fork", "unable to");
2713 execvp (mailproc
, vec
);
2714 fprintf (stderr
, "unable to exec ");
2720 if (pidXwait (child_id
, NULL
) == OK
)
2721 advise (NULL
, "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
= add (tempfile
, NULL
);
2734 ce
->ce_file
= add (*file
, NULL
);
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. */
2746 free (ct
->c_showproc
);
2747 ct
->c_showproc
= add ("true", NULL
);
2749 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2750 *file
= ce
->ce_file
;
2751 return fileno (ce
->ce_fp
);
2762 return init_encoding (ct
, openURL
);
2767 openURL (CT ct
, char **file
)
2769 struct exbody
*e
= ct
->c_ctexbody
;
2770 CE ce
= &ct
->c_cefile
;
2771 char *urlprog
, *program
;
2772 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2773 int fd
, caching
, cachetype
;
2774 struct msgs_array args
= { 0, 0, NULL
};
2777 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2781 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2785 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2797 content_error(NULL
, ct
, "missing url parameter");
2801 ce
->ce_unlink
= (*file
== NULL
);
2803 cachefile
[0] = '\0';
2805 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2806 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2807 if (*file
== NULL
) {
2814 ce
->ce_file
= add(*file
, NULL
);
2816 ce
->ce_file
= add(cachefile
, NULL
);
2819 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2820 adios(NULL
, "unable to create temporary file in %s",
2823 ce
->ce_file
= add (tempfile
, NULL
);
2826 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2827 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2831 switch (child_id
= fork()) {
2833 adios ("fork", "unable to");
2837 argsplit_msgarg(&args
, urlprog
, &program
);
2838 app_msgarg(&args
, e
->eb_url
);
2839 app_msgarg(&args
, NULL
);
2840 dup2(fileno(ce
->ce_fp
), 1);
2841 close(fileno(ce
->ce_fp
));
2842 execvp(program
, args
.msgs
);
2843 fprintf(stderr
, "Unable to exec ");
2849 if (pidXwait(child_id
, NULL
)) {
2857 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2862 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2863 if ((fp
= fopen(cachefile
, "w"))) {
2865 FILE *gp
= ce
->ce_fp
;
2867 fseeko(gp
, 0, SEEK_SET
);
2869 while ((cc
= fread(buffer
, sizeof(*buffer
),
2870 sizeof(buffer
), gp
)) > 0)
2871 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2872 advise ("openURL", "fwrite");
2878 admonish(ce
->ce_file
, "error reading");
2879 (void) m_unlink (cachefile
);
2886 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2887 *file
= ce
->ce_file
;
2893 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2894 * has to be base64 decoded.
2897 readDigest (CT ct
, char *cp
)
2899 unsigned char *digest
;
2902 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2903 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2905 if (strlen ((char *) digest
) <= maxlen
) {
2906 memcpy (ct
->c_digest
, digest
, maxlen
);
2911 fprintf (stderr
, "MD5 digest=");
2912 for (i
= 0; i
< maxlen
; ++i
) {
2913 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2915 fprintf (stderr
, "\n");
2921 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2922 (int) strlen ((char *) digest
));
2932 /* Multipart parts might have content before the first subpart and/or
2933 after the last subpart that hasn't been stored anywhere else, so do
2936 get_leftover_mp_content (CT ct
, int before
/* or after */)
2938 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2940 int found_boundary
= 0;
2946 char *content
= NULL
;
2948 if (! m
) return NOTOK
;
2951 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2953 /* Isolate the beginning of this part to the beginning of the
2954 first subpart and save any content between them. */
2955 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2956 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2957 boundary
= concat ("--", m
->mp_start
, NULL
);
2959 struct part
*last_subpart
= NULL
;
2960 struct part
*subpart
;
2962 /* Go to the last subpart to get its end position. */
2963 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2964 last_subpart
= subpart
;
2967 if (last_subpart
== NULL
) return NOTOK
;
2969 /* Isolate the end of the last subpart to the end of this part
2970 and save any content between them. */
2971 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2972 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2973 boundary
= concat ("--", m
->mp_stop
, NULL
);
2976 /* Back up by 1 to pick up the newline. */
2977 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2979 /* Don't look beyond beginning of first subpart (before) or
2980 next part (after). */
2981 if (read
> max
) bufp
[read
-max
] = '\0';
2984 if (! strcmp (bufp
, boundary
)) {
2988 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2994 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2996 char *old_content
= content
;
2997 content
= concat (content
, bufp
, NULL
);
3001 ? concat ("\n", bufp
, NULL
)
3002 : concat (bufp
, NULL
);
3007 if (found_boundary
|| read
> max
) break;
3009 if (read
> max
) break;
3013 /* Skip the newline if that's all there is. */
3017 /* Remove trailing newline, except at EOF. */
3018 if ((before
|| ! feof (ct
->c_fp
)) &&
3019 (cp
= content
+ strlen (content
)) > content
&&
3024 if (strlen (content
) > 1) {
3026 m
->mp_content_before
= content
;
3028 m
->mp_content_after
= content
;
3043 ct_type_str (int type
) {
3045 case CT_APPLICATION
:
3046 return "application";
3062 return "unknown_type";
3068 ct_subtype_str (int type
, int subtype
) {
3070 case CT_APPLICATION
:
3072 case APPLICATION_OCTETS
:
3074 case APPLICATION_POSTSCRIPT
:
3075 return "postscript";
3077 return "unknown_app_subtype";
3081 case MESSAGE_RFC822
:
3083 case MESSAGE_PARTIAL
:
3085 case MESSAGE_EXTERNAL
:
3088 return "unknown_msg_subtype";
3094 case MULTI_ALTERNATE
:
3095 return "alternative";
3098 case MULTI_PARALLEL
:
3103 return "unknown_multipart_subtype";
3114 return "unknown_text_subtype";
3117 return "unknown_type";
3123 ct_str_type (const char *type
) {
3124 struct str2init
*s2i
;
3126 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3127 if (! strcasecmp (type
, s2i
->si_key
)) {
3131 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3140 ct_str_subtype (int type
, const char *subtype
) {
3144 case CT_APPLICATION
:
3145 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3146 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3150 return kv
->kv_value
;
3152 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3153 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3157 return kv
->kv_value
;
3159 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3160 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3164 return kv
->kv_value
;
3166 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3167 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3171 return kv
->kv_value
;
3178 /* Find the content type and InitFunc for the CT. */
3179 const struct str2init
*
3180 get_ct_init (int type
) {
3181 const struct str2init
*sp
;
3183 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3184 if (type
== sp
->si_val
) {
3193 ce_str (int encoding
) {
3198 return "quoted-printable";
3214 /* Find the content type and InitFunc for the content encoding method. */
3215 const struct str2init
*
3216 get_ce_method (const char *method
) {
3217 struct str2init
*sp
;
3219 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3220 if (! strcasecmp (method
, sp
->si_key
)) {
3229 * Parse a series of MIME attributes (or parameters) given a header as
3232 * Arguments include:
3234 * filename - Name of input file (for error messages)
3235 * fieldname - Name of field being processed
3236 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3237 * Updated to point to end of attributes when finished.
3238 * param_head - Pointer to head of parameter list
3239 * param_tail - Pointer to tail of parameter list
3240 * commentp - Pointer to header comment pointer (may be NULL)
3242 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3243 * DONE to indicate a benign error (minor parsing error, but the program
3248 parse_header_attrs (const char *filename
, const char *fieldname
,
3249 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3252 char *cp
= *header_attrp
;
3258 struct sectlist
*next
;
3264 struct sectlist
*sechead
;
3265 struct parmlist
*next
;
3266 } *pp
, *pp2
, *phead
= NULL
;
3268 while (*cp
== ';') {
3269 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3270 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3273 while (isspace ((unsigned char) *cp
))
3277 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3282 if (! suppress_extraneous_trailing_semicolon_warning
) {
3284 "extraneous trailing ';' in message %s's %s: "
3286 filename
, fieldname
);
3288 extraneous_trailing_semicolon
= 1;
3292 /* down case the attribute name */
3293 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3294 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
3295 *dp
= tolower ((unsigned char) *dp
);
3297 for (up
= dp
; isspace ((unsigned char) *dp
);)
3299 if (dp
== cp
|| *dp
!= '=') {
3301 "invalid parameter in message %s's %s: "
3302 "field\n%*sparameter %s (error detected at offset %d)",
3303 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3308 * To handle RFC 2231, we have to deal with the following extensions:
3310 * name*=encoded-value
3311 * name*<N>=part-N-of-a-parameter-value
3312 * name*<N>*=encoded-part-N-of-a-parameter-value
3315 * If there's a * right before the equal sign, it's encoded.
3316 * If there's a * and one or more digits, then it's section N.
3318 * Remember we can have one or the other, or both. cp points to
3319 * beginning of name, up points past the last character in the
3323 for (vp
= cp
; vp
< up
; vp
++) {
3324 if (*vp
== '*' && vp
< up
- 1) {
3327 } else if (*vp
== '*' && vp
== up
- 1) {
3329 } else if (partial
) {
3330 if (isdigit((unsigned char) *vp
))
3331 index
= *vp
- '0' + index
* 10;
3333 advise (NULL
, "invalid parameter index in message %s's "
3334 "%s: field\n%*s(parameter %s)", filename
,
3335 fieldname
, strlen(invo_name
) + 2, "", cp
);
3344 * Break out the parameter name and value sections and allocate
3348 nameptr
= mh_xmalloc(len
+ 1);
3349 strncpy(nameptr
, cp
, len
);
3350 nameptr
[len
] = '\0';
3352 for (dp
++; isspace ((unsigned char) *dp
);)
3357 * Single quotes delimit the character set and language tag.
3358 * They are required on the first section (or a complete
3363 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3369 charset
= mh_xmalloc(len
+ 1);
3370 strncpy(charset
, dp
, len
);
3371 charset
[len
] = '\0';
3377 advise(NULL
, "missing charset in message %s's %s: "
3378 "field\n%*s(parameter %s)", filename
, fieldname
,
3379 strlen(invo_name
) + 2, "", nameptr
);
3385 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3392 lang
= mh_xmalloc(len
+ 1);
3393 strncpy(lang
, dp
, len
);
3400 advise(NULL
, "missing language tag in message %s's %s: "
3401 "field\n%*s(parameter %s)", filename
, fieldname
,
3402 strlen(invo_name
) + 2, "", nameptr
);
3413 * At this point vp should be pointing at the beginning
3414 * of the encoded value/section. Continue until we reach
3415 * the end or get whitespace. But first, calculate the
3416 * length so we can allocate the correct buffer size.
3419 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3421 if (*(vp
+ 1) == '\0' ||
3422 !isxdigit((unsigned char) *(vp
+ 1)) ||
3423 *(vp
+ 2) == '\0' ||
3424 !isxdigit((unsigned char) *(vp
+ 2))) {
3425 advise(NULL
, "invalid encoded sequence in message "
3426 "%s's %s: field\n%*s(parameter %s)",
3427 filename
, fieldname
, strlen(invo_name
) + 2,
3441 up
= valptr
= mh_xmalloc(len
+ 1);
3443 for (vp
= dp
; istoken(*vp
); vp
++) {
3445 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3456 * A "normal" string. If it's got a leading quote, then we
3457 * strip the quotes out. Otherwise go until we reach the end
3458 * or get whitespace. Note we scan it twice; once to get the
3459 * length, then the second time copies it into the destination
3466 for (cp
= dp
+ 1;;) {
3471 "invalid quoted-string in message %s's %s: "
3472 "field\n%*s(parameter %s)",
3473 filename
, fieldname
, strlen(invo_name
) + 2, "",
3496 for (cp
= dp
; istoken (*cp
); cp
++) {
3501 valptr
= mh_xmalloc(len
+ 1);
3505 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3513 strncpy(valptr
, cp
= dp
, len
);
3521 * If 'partial' is set, we don't allocate a parameter now. We
3522 * put it on the parameter linked list to be reassembled later.
3524 * "phead" points to a list of all parameters we need to reassemble.
3525 * Each parameter has a list of sections. We insert the sections in
3530 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3531 if (strcasecmp(nameptr
, pp
->name
) == 0)
3543 * Insert this into the section linked list
3551 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3552 sp
->next
= pp
->sechead
;
3555 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3556 if (sp2
->index
== sp
->index
) {
3557 advise (NULL
, "duplicate index (%d) in message "
3558 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3559 filename
, fieldname
, strlen(invo_name
) + 2, "",
3564 if (sp2
->index
< sp
->index
&&
3565 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3566 sp
->next
= sp2
->next
;
3573 advise(NULL
, "Internal error: cannot insert partial "
3574 "param in message %s's %s: field\n%*s(parameter %s)",
3575 filename
, fieldname
, strlen(invo_name
) + 2, "",
3583 * Save our charset and lang tags.
3586 if (index
== 0 && encoded
) {
3589 pp
->charset
= charset
;
3595 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3596 pm
->pm_charset
= charset
;
3600 while (isspace ((unsigned char) *cp
))
3604 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3610 * Now that we're done, reassemble all of the partial parameters.
3613 for (pp
= phead
; pp
!= NULL
; ) {
3617 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3618 if (sp
->index
!= pindex
++) {
3619 advise(NULL
, "missing section %d for parameter in "
3620 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3621 filename
, fieldname
, strlen(invo_name
) + 2, "",
3628 p
= q
= mh_xmalloc(tlen
+ 1);
3629 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3630 memcpy(q
, sp
->value
, sp
->len
);
3640 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3641 pm
->pm_charset
= pp
->charset
;
3642 pm
->pm_lang
= pp
->lang
;
3653 * Return the charset for a particular content type.
3657 content_charset (CT ct
) {
3658 char *ret_charset
= NULL
;
3660 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3662 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3667 * Create a string based on a list of output parameters. Assume that this
3668 * parameter string will be appended to an existing header, so start out
3669 * with the separator (;). Perform RFC 2231 encoding when necessary.
3673 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3675 char *paramout
= NULL
;
3676 char line
[CPERLIN
* 2], *q
;
3677 int curlen
, index
, cont
, encode
, i
;
3678 size_t valoff
, numchars
;
3680 while (params
!= NULL
) {
3686 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3689 if (strlen(params
->pm_name
) > CPERLIN
) {
3690 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3696 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3699 * Loop until we get a parameter that fits within a line. We
3700 * assume new lines start with a tab, so check our overflow based
3710 * At this point we're definitely continuing the line, so
3711 * be sure to include the parameter name and section index.
3714 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3715 params
->pm_name
, index
);
3718 * Both of these functions do a NUL termination
3722 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3723 numchars
, valoff
, index
);
3725 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
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 contined 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
);
3798 paramout
= add(line
, paramout
);
3799 initialwidth
+= strlen(line
);
3801 params
= params
->pm_next
;
3805 *offsetout
= initialwidth
;
3811 * Calculate the size of a parameter.
3815 * pm - The parameter being output
3816 * index - If continuing the parameter, the index of the section
3818 * valueoff - The current offset into the parameter value that we're
3819 * working on (previous sections have consumed valueoff bytes).
3820 * encode - Set if we should perform encoding on this parameter section
3821 * (given that we're consuming bytesfit bytes).
3822 * cont - Set if the remaining data in value will not fit on a single
3823 * line and will need to be continued.
3824 * bytesfit - The number of bytes that we can consume from the parameter
3825 * value and still fit on a completely new line. The
3826 * calculation assumes the new line starts with a tab,
3827 * includes the parameter name and any encoding, and fits
3828 * within CPERLIN bytes. Will always be at least 1.
3832 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3835 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3836 size_t len
= 0, fit
= 0;
3837 int fitlimit
= 0, eightbit
, maxfit
;
3842 * Add up the length. First, start with the parameter name.
3845 len
= strlen(pm
->pm_name
);
3848 * Scan the parameter value and see if we need to do encoding for this
3852 eightbit
= contains8bit(start
, NULL
);
3855 * Determine if we need to encode this section. Encoding is necessary if:
3857 * - There are any 8-bit characters at all and we're on the first
3859 * - There are 8-bit characters within N bytes of our section start.
3860 * N is calculated based on the number of bytes it would take to
3861 * reach CPERLIN. Specifically:
3862 * 8 (starting tab) +
3863 * strlen(param name) +
3864 * 4 ('* for section marker, '=', opening/closing '"')
3866 * is the number of bytes used by everything that isn't part of the
3867 * value. So that gets subtracted from CPERLIN.
3870 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3871 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3872 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3876 len
++; /* Add in equal sign */
3880 * We're using maxfit as a marker for how many characters we can
3881 * fit into the line. Bump it by two because we're not using quotes
3888 * If we don't have a charset or language tag in this parameter,
3892 if (! pm
->pm_charset
) {
3893 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3894 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3895 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3896 "local character set is US-ASCII", pm
->pm_name
);
3899 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3901 len
++; /* For the encoding marker */
3904 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3909 * We know we definitely need to include an index. maxfit already
3910 * includes the section marker.
3912 len
+= strlen(indexchar
);
3914 for (p
= start
; *p
!= '\0'; p
++) {
3915 if (isparamencode(*p
)) {
3923 * Just so there's no confusion: maxfit is counting OUTPUT
3924 * characters (post-encoding). fit is counting INPUT characters.
3926 if (! fitlimit
&& maxfit
>= 0)
3928 else if (! fitlimit
)
3933 * Calculate the string length, but add room for quoting \
3934 * and " if necessary. Also account for quotes at beginning
3937 for (p
= start
; *p
!= '\0'; p
++) {
3948 if (! fitlimit
&& maxfit
>= 0)
3950 else if (! fitlimit
)
3967 * Output an encoded parameter string.
3971 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3972 size_t valueoff
, int index
)
3974 size_t outlen
= 0, n
;
3975 char *endptr
= output
+ len
, *p
;
3978 * First, output the marker for an encoded string.
3986 * If the index is 0, output the character set and language tag.
3987 * If theses were NULL, they should have already been filled in
3992 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3996 if (output
> endptr
) {
3997 advise(NULL
, "Internal error: parameter buffer overflow");
4003 * Copy over the value, encoding if necessary
4006 p
= pm
->pm_value
+ valueoff
;
4007 while (valuelen
-- > 0) {
4008 if (isparamencode(*p
)) {
4009 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4016 if (output
> endptr
) {
4017 advise(NULL
, "Internal error: parameter buffer overflow");
4028 * Output a "normal" parameter, without encoding. Be sure to escape
4029 * quotes and backslashes if necessary.
4033 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4037 char *endptr
= output
+ len
, *p
;
4043 p
= pm
->pm_value
+ valueoff
;
4045 while (valuelen
-- > 0) {
4055 if (output
> endptr
) {
4056 advise(NULL
, "Internal error: parameter buffer overflow");
4061 if (output
- 2 > endptr
) {
4062 advise(NULL
, "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
4148 char *get_param_value(PM pm
, char replace
)
4150 static char buffer
[4096]; /* I hope no parameters are larger */
4151 size_t bufsize
= sizeof(buffer
);
4156 ICONV_CONST
char *p
;
4157 #else /* HAVE_ICONV */
4159 #endif /* HAVE_ICONV */
4164 * If we don't have a character set indicated, it's assumed to be
4165 * US-ASCII. If it matches our character set, we don't need to convert
4169 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4170 strlen(pm
->pm_charset
))) {
4171 return pm
->pm_value
;
4175 * In this case, we need to convert. If we have iconv support, use
4176 * that. Otherwise, go through and simply replace every non-ASCII
4177 * character with the substitution character.
4182 bufsize
= sizeof(buffer
);
4183 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4185 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4186 if (cd
== (iconv_t
) -1) {
4190 inbytes
= strlen(pm
->pm_value
);
4194 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4195 if (errno
!= EILSEQ
) {
4200 * Reset shift state, substitute our character,
4201 * try to restart conversion.
4204 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4217 for (++p
, --inbytes
;
4218 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4237 #endif /* HAVE_ICONV */
4240 * Take everything non-ASCII and substituite the replacement character
4244 bufsize
= sizeof(buffer
);
4245 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4246 if (isascii((unsigned char) *p
) && !iscntrl((unsigned char) *p
))