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)
1211 if (! suppress_bogus_mp_content_warning
) {
1212 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1214 bogus_mp_content
= 1;
1216 if (!inout
&& part
) {
1218 p
->c_end
= ct
->c_end
;
1220 if (p
->c_begin
>= p
->c_end
) {
1221 for (next
= &m
->mp_parts
; *next
!= part
;
1222 next
= &((*next
)->mp_next
))
1226 free ((char *) part
);
1231 /* reverse the order of the parts for multipart/alternative */
1232 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1238 * label all subparts with part number, and
1239 * then initialize the content of the subpart.
1244 char partnam
[BUFSIZ
];
1247 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1248 pp
= partnam
+ strlen (partnam
);
1253 for (part
= m
->mp_parts
, partnum
= 1; part
;
1254 part
= part
->mp_next
, partnum
++) {
1257 sprintf (pp
, "%d", partnum
);
1258 p
->c_partno
= add (partnam
, NULL
);
1260 /* initialize the content of the subparts */
1261 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1270 get_leftover_mp_content (ct
, 1);
1271 get_leftover_mp_content (ct
, 0);
1281 * reverse the order of the parts of a multipart/alternative,
1282 * presumably to put the "most favored" alternative first, for
1283 * ease of choosing/displaying it later on. from a mail message on
1284 * nmh-workers, from kenh:
1285 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1286 * see code in mhn that did the same thing... Acccording to the RCS
1287 * logs, that code was around from the initial checkin of mhn.c by
1288 * John Romine in 1992, which is as far back as we have."
1291 reverse_parts (CT ct
)
1293 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1297 /* Reverse the order of its parts by walking the mp_parts list
1298 and pushing each node to the front. */
1299 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1300 next
= part
->mp_next
;
1301 part
->mp_next
= m
->mp_parts
;
1307 move_preferred_part (CT ct
, char *type
, char *subtype
)
1309 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1310 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1314 /* move the matching part(s) to the head of the list: walk the
1315 * list of parts, move matching parts to a new list (maintaining
1316 * their order), and finally, concatenate the old list onto the
1323 head
->mp_next
= m
->mp_parts
;
1324 nhead
->mp_next
= NULL
;
1328 part
= head
->mp_next
;
1329 while (part
!= NULL
) {
1330 ci
= &part
->mp_part
->c_ctinfo
;
1331 if (!strcasecmp(ci
->ci_type
, type
) &&
1332 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1333 prev
->mp_next
= part
->mp_next
;
1334 part
->mp_next
= NULL
;
1335 ntail
->mp_next
= part
;
1337 part
= prev
->mp_next
;
1340 part
= prev
->mp_next
;
1343 ntail
->mp_next
= head
->mp_next
;
1344 m
->mp_parts
= nhead
->mp_next
;
1349 * move parts that match the user's preferences (-prefer) to the head
1350 * of the line. process preferences in reverse so first one given
1351 * ends up first in line
1357 for (i
= npreferred
-1; i
>= 0; i
--)
1358 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1363 /* parse_mime() arranges alternates in reverse (priority) order. This
1364 function can be used to reverse them back. This will put, for
1365 example, a text/plain part before a text/html part in a
1366 multipart/alternative part, for example, where it belongs. */
1368 reverse_alternative_parts (CT ct
) {
1369 if (ct
->c_type
== CT_MULTIPART
) {
1370 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1373 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1377 /* And call recursively on each part of a multipart. */
1378 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1379 reverse_alternative_parts (part
->mp_part
);
1392 CI ci
= &ct
->c_ctinfo
;
1394 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1396 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1397 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1401 /* check for missing subtype */
1402 if (!*ci
->ci_subtype
)
1403 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1406 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1408 switch (ct
->c_subtype
) {
1409 case MESSAGE_RFC822
:
1412 case MESSAGE_PARTIAL
:
1418 ct
->c_ctparams
= (void *) p
;
1420 /* scan for parameters "id", "number", and "total" */
1421 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1422 if (!strcasecmp (pm
->pm_name
, "id")) {
1423 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1426 if (!strcasecmp (pm
->pm_name
, "number")) {
1427 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1428 || p
->pm_partno
< 1) {
1431 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1432 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1433 ct
->c_file
, TYPE_FIELD
);
1438 if (!strcasecmp (pm
->pm_name
, "total")) {
1439 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1448 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1450 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1451 ci
->ci_type
, ci
->ci_subtype
,
1452 ct
->c_file
, TYPE_FIELD
);
1458 case MESSAGE_EXTERNAL
:
1466 ct
->c_ctparams
= (void *) e
;
1469 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1470 advise (ct
->c_file
, "unable to open for reading");
1474 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1476 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1484 p
->c_ceopenfnx
= NULL
;
1485 if ((exresult
= params_external (ct
, 0)) != NOTOK
1486 && p
->c_ceopenfnx
== openMail
) {
1490 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1492 content_error (NULL
, ct
,
1493 "empty body for access-type=mail-server");
1497 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1498 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1500 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1502 adios ("failed", "fread");
1505 adios (NULL
, "unexpected EOF from fread");
1508 bp
+= cc
, size
-= cc
;
1515 p
->c_end
= p
->c_begin
;
1520 if (exresult
== NOTOK
)
1522 if (e
->eb_flags
== NOTOK
)
1525 switch (p
->c_type
) {
1530 if (p
->c_subtype
!= MESSAGE_RFC822
)
1534 e
->eb_partno
= ct
->c_partno
;
1536 (*p
->c_ctinitfnx
) (p
);
1551 params_external (CT ct
, int composing
)
1554 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1555 CI ci
= &ct
->c_ctinfo
;
1557 ct
->c_ceopenfnx
= NULL
;
1558 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1559 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1560 struct str2init
*s2i
;
1561 CT p
= e
->eb_content
;
1563 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1564 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1567 e
->eb_access
= pm
->pm_value
;
1568 e
->eb_flags
= NOTOK
;
1569 p
->c_encoding
= CE_EXTERNAL
;
1572 e
->eb_access
= s2i
->si_key
;
1573 e
->eb_flags
= s2i
->si_val
;
1574 p
->c_encoding
= CE_EXTERNAL
;
1576 /* Call the Init function for this external type */
1577 if ((*s2i
->si_init
)(p
) == NOTOK
)
1581 if (!strcasecmp (pm
->pm_name
, "name")) {
1582 e
->eb_name
= pm
->pm_value
;
1585 if (!strcasecmp (pm
->pm_name
, "permission")) {
1586 e
->eb_permission
= pm
->pm_value
;
1589 if (!strcasecmp (pm
->pm_name
, "site")) {
1590 e
->eb_site
= pm
->pm_value
;
1593 if (!strcasecmp (pm
->pm_name
, "directory")) {
1594 e
->eb_dir
= pm
->pm_value
;
1597 if (!strcasecmp (pm
->pm_name
, "mode")) {
1598 e
->eb_mode
= pm
->pm_value
;
1601 if (!strcasecmp (pm
->pm_name
, "size")) {
1602 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1605 if (!strcasecmp (pm
->pm_name
, "server")) {
1606 e
->eb_server
= pm
->pm_value
;
1609 if (!strcasecmp (pm
->pm_name
, "subject")) {
1610 e
->eb_subject
= pm
->pm_value
;
1613 if (!strcasecmp (pm
->pm_name
, "url")) {
1615 * According to RFC 2017, we have to remove all whitespace from
1619 char *u
, *p
= pm
->pm_value
;
1620 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1622 for (; *p
!= '\0'; p
++) {
1623 if (! isspace((unsigned char) *p
))
1630 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1631 e
->eb_body
= getcpy (pm
->pm_value
);
1636 if (!e
->eb_access
) {
1638 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1639 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1652 InitApplication (CT ct
)
1654 CI ci
= &ct
->c_ctinfo
;
1657 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1664 * TRANSFER ENCODINGS
1668 init_encoding (CT ct
, OpenCEFunc openfnx
)
1670 ct
->c_ceopenfnx
= openfnx
;
1671 ct
->c_ceclosefnx
= close_encoding
;
1672 ct
->c_cesizefnx
= size_encoding
;
1679 close_encoding (CT ct
)
1681 CE ce
= &ct
->c_cefile
;
1690 static unsigned long
1691 size_encoding (CT ct
)
1696 CE ce
= &ct
->c_cefile
;
1699 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1700 return (long) st
.st_size
;
1703 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1704 return (long) st
.st_size
;
1708 if (ct
->c_encoding
== CE_EXTERNAL
)
1709 return (ct
->c_end
- ct
->c_begin
);
1712 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1713 return (ct
->c_end
- ct
->c_begin
);
1715 if (fstat (fd
, &st
) != NOTOK
)
1716 size
= (long) st
.st_size
;
1720 (*ct
->c_ceclosefnx
) (ct
);
1732 return init_encoding (ct
, openBase64
);
1737 openBase64 (CT ct
, char **file
)
1740 int fd
, own_ct_fp
= 0;
1741 char *cp
, *buffer
= NULL
;
1742 /* sbeck -- handle suffixes */
1744 CE ce
= &ct
->c_cefile
;
1745 unsigned char *decoded
;
1747 unsigned char digest
[16];
1750 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1755 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1756 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1762 if (*file
== NULL
) {
1765 ce
->ce_file
= add (*file
, NULL
);
1769 /* sbeck@cise.ufl.edu -- handle suffixes */
1771 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1772 if (ce
->ce_unlink
) {
1773 /* Create temporary file with filename extension. */
1774 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1775 adios(NULL
, "unable to create temporary file in %s",
1779 ce
->ce_file
= add (cp
, ce
->ce_file
);
1781 } else if (*file
== NULL
) {
1783 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1784 adios(NULL
, "unable to create temporary file in %s",
1787 ce
->ce_file
= add (tempfile
, NULL
);
1790 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1791 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1795 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1796 adios (NULL
, "internal error(1)");
1798 buffer
= mh_xmalloc (len
+ 1);
1801 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1802 content_error (ct
->c_file
, ct
, "unable to open for reading");
1808 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1811 switch (cc
= read (fd
, cp
, len
)) {
1813 content_error (ct
->c_file
, ct
, "error reading from");
1817 content_error (NULL
, ct
, "premature eof");
1828 /* decodeBase64() requires null-terminated input. */
1831 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1832 ct
->c_digested
? digest
: NULL
) == OK
) {
1834 unsigned char *decoded_p
= decoded
;
1835 for (i
= 0; i
< decoded_len
; ++i
) {
1836 putc (*decoded_p
++, ce
->ce_fp
);
1838 free ((char *) decoded
);
1839 if (ferror (ce
->ce_fp
)) {
1840 content_error (ce
->ce_file
, ct
, "error writing to");
1844 if (ct
->c_digested
) {
1845 if (memcmp(digest
, ct
->c_digest
,
1846 sizeof(digest
) / sizeof(digest
[0]))) {
1847 content_error (NULL
, ct
,
1848 "content integrity suspect (digest mismatch) -- continuing");
1851 fprintf (stderr
, "content integrity confirmed\n");
1859 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1861 if (fflush (ce
->ce_fp
)) {
1862 content_error (ce
->ce_file
, ct
, "error writing to");
1866 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1869 *file
= ce
->ce_file
;
1875 return fileno (ce
->ce_fp
);
1882 free_encoding (ct
, 0);
1892 static char hex2nib
[0x80] = {
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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1899 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1900 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1901 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1915 return init_encoding (ct
, openQuoted
);
1920 openQuoted (CT ct
, char **file
)
1922 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1928 CE ce
= &ct
->c_cefile
;
1929 /* sbeck -- handle suffixes */
1934 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1939 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1940 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1946 if (*file
== NULL
) {
1949 ce
->ce_file
= add (*file
, NULL
);
1953 /* sbeck@cise.ufl.edu -- handle suffixes */
1955 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1956 if (ce
->ce_unlink
) {
1957 /* Create temporary file with filename extension. */
1958 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1959 adios(NULL
, "unable to create temporary file in %s",
1963 ce
->ce_file
= add (cp
, ce
->ce_file
);
1965 } else if (*file
== NULL
) {
1967 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1968 adios(NULL
, "unable to create temporary file in %s",
1971 ce
->ce_file
= add (tempfile
, NULL
);
1974 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1975 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1979 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1980 adios (NULL
, "internal error(2)");
1983 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1984 content_error (ct
->c_file
, ct
, "unable to open for reading");
1990 if ((digested
= ct
->c_digested
))
1991 MD5Init (&mdContext
);
1998 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2000 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2001 content_error (NULL
, ct
, "premature eof");
2005 if ((cc
= gotlen
) > len
)
2009 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2010 if (!isspace ((unsigned char) *ep
))
2014 for (; cp
< ep
; cp
++) {
2016 /* in an escape sequence */
2018 /* at byte 1 of an escape sequence */
2019 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2020 /* next is byte 2 */
2023 /* at byte 2 of an escape sequence */
2025 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2026 putc (mask
, ce
->ce_fp
);
2028 MD5Update (&mdContext
, &mask
, 1);
2029 if (ferror (ce
->ce_fp
)) {
2030 content_error (ce
->ce_file
, ct
, "error writing to");
2033 /* finished escape sequence; next may be literal or a new
2034 * escape sequence */
2037 /* on to next byte */
2041 /* not in an escape sequence */
2043 /* starting an escape sequence, or invalid '='? */
2044 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2045 /* "=\n" soft line break, eat the \n */
2049 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2050 /* We don't have 2 bytes left, so this is an invalid
2051 * escape sequence; just show the raw bytes (below). */
2052 } else if (isxdigit ((unsigned char) cp
[1]) &&
2053 isxdigit ((unsigned char) cp
[2])) {
2054 /* Next 2 bytes are hex digits, making this a valid escape
2055 * sequence; let's decode it (above). */
2059 /* One or both of the next 2 is out of range, making this
2060 * an invalid escape sequence; just show the raw bytes
2065 /* Just show the raw byte. */
2066 putc (*cp
, ce
->ce_fp
);
2069 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2071 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2074 if (ferror (ce
->ce_fp
)) {
2075 content_error (ce
->ce_file
, ct
, "error writing to");
2081 content_error (NULL
, ct
,
2082 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2086 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2088 if (fflush (ce
->ce_fp
)) {
2089 content_error (ce
->ce_file
, ct
, "error writing to");
2094 unsigned char digest
[16];
2096 MD5Final (digest
, &mdContext
);
2097 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2098 sizeof(digest
) / sizeof(digest
[0])))
2099 content_error (NULL
, ct
,
2100 "content integrity suspect (digest mismatch) -- continuing");
2103 fprintf (stderr
, "content integrity confirmed\n");
2106 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2109 *file
= ce
->ce_file
;
2115 return fileno (ce
->ce_fp
);
2118 free_encoding (ct
, 0);
2135 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2138 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2144 open7Bit (CT ct
, char **file
)
2146 int cc
, fd
, len
, own_ct_fp
= 0;
2147 char buffer
[BUFSIZ
];
2148 /* sbeck -- handle suffixes */
2151 CE ce
= &ct
->c_cefile
;
2154 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2159 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2160 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2166 if (*file
== NULL
) {
2169 ce
->ce_file
= add (*file
, NULL
);
2173 /* sbeck@cise.ufl.edu -- handle suffixes */
2175 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2176 if (ce
->ce_unlink
) {
2177 /* Create temporary file with filename extension. */
2178 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2179 adios(NULL
, "unable to create temporary file in %s",
2183 ce
->ce_file
= add (cp
, ce
->ce_file
);
2185 } else if (*file
== NULL
) {
2187 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2188 adios(NULL
, "unable to create temporary file in %s",
2191 ce
->ce_file
= add (tempfile
, NULL
);
2194 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2195 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2199 if (ct
->c_type
== CT_MULTIPART
) {
2200 CI ci
= &ct
->c_ctinfo
;
2204 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2205 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2206 + 1 + strlen (ci
->ci_subtype
);
2207 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2210 fputs (buffer
, ce
->ce_fp
);
2214 if (ci
->ci_comment
) {
2215 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2216 fputs ("\n\t", ce
->ce_fp
);
2220 putc (' ', ce
->ce_fp
);
2223 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2226 fprintf (ce
->ce_fp
, "\n");
2228 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2230 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2232 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2233 fprintf (ce
->ce_fp
, "\n");
2236 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2237 adios (NULL
, "internal error(3)");
2240 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2241 content_error (ct
->c_file
, ct
, "unable to open for reading");
2247 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2249 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2251 content_error (ct
->c_file
, ct
, "error reading from");
2255 content_error (NULL
, ct
, "premature eof");
2263 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2264 advise ("open7Bit", "fwrite");
2266 if (ferror (ce
->ce_fp
)) {
2267 content_error (ce
->ce_file
, ct
, "error writing to");
2272 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2274 if (fflush (ce
->ce_fp
)) {
2275 content_error (ce
->ce_file
, ct
, "error writing to");
2279 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2282 *file
= ce
->ce_file
;
2287 return fileno (ce
->ce_fp
);
2290 free_encoding (ct
, 0);
2304 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2306 char cachefile
[BUFSIZ
];
2309 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2314 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2315 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2321 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2322 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2323 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2324 ce
->ce_file
= mh_xstrdup(cachefile
);
2328 admonish (cachefile
, "unable to fopen for reading");
2332 *fd
= fileno (ce
->ce_fp
);
2336 *file
= ce
->ce_file
;
2337 *fd
= fileno (ce
->ce_fp
);
2348 return init_encoding (ct
, openFile
);
2353 openFile (CT ct
, char **file
)
2356 char cachefile
[BUFSIZ
];
2357 struct exbody
*e
= ct
->c_ctexbody
;
2358 CE ce
= &ct
->c_cefile
;
2360 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2372 content_error (NULL
, ct
, "missing name parameter");
2376 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2379 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2380 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2384 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2385 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2386 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2390 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2391 if ((fp
= fopen (cachefile
, "w"))) {
2393 char buffer
[BUFSIZ
];
2394 FILE *gp
= ce
->ce_fp
;
2396 fseek (gp
, 0L, SEEK_SET
);
2398 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2400 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2401 advise ("openFile", "fwrite");
2406 admonish (ce
->ce_file
, "error reading");
2407 (void) m_unlink (cachefile
);
2411 admonish (cachefile
, "error writing");
2412 (void) m_unlink (cachefile
);
2419 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2420 *file
= ce
->ce_file
;
2421 return fileno (ce
->ce_fp
);
2431 return init_encoding (ct
, openFTP
);
2436 openFTP (CT ct
, char **file
)
2438 int cachetype
, caching
, fd
;
2440 char *bp
, *ftp
, *user
, *pass
;
2441 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2443 CE ce
= &ct
->c_cefile
;
2444 static char *username
= NULL
;
2445 static char *password
= NULL
;
2449 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2455 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2466 if (!e
->eb_name
|| !e
->eb_site
) {
2467 content_error (NULL
, ct
, "missing %s parameter",
2468 e
->eb_name
? "site": "name");
2472 /* Get the buffer ready to go */
2474 buflen
= sizeof(buffer
);
2477 * Construct the query message for user
2479 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2485 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2491 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2492 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2497 if (e
->eb_size
> 0) {
2498 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2503 snprintf (bp
, buflen
, "? ");
2506 * Now, check the answer
2508 if (!read_yes_or_no_if_tty (buffer
))
2513 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2517 ruserpass (e
->eb_site
, &username
, &password
, 0);
2522 ce
->ce_unlink
= (*file
== NULL
);
2524 cachefile
[0] = '\0';
2525 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2526 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2527 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2528 if (*file
== NULL
) {
2535 ce
->ce_file
= add (*file
, NULL
);
2537 ce
->ce_file
= add (cachefile
, NULL
);
2540 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2541 adios(NULL
, "unable to create temporary file in %s",
2544 ce
->ce_file
= add (tempfile
, NULL
);
2547 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2548 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2553 int child_id
, i
, vecp
;
2557 vec
[vecp
++] = r1bindex (ftp
, '/');
2558 vec
[vecp
++] = e
->eb_site
;
2561 vec
[vecp
++] = e
->eb_dir
;
2562 vec
[vecp
++] = e
->eb_name
;
2563 vec
[vecp
++] = ce
->ce_file
,
2564 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2565 ? "ascii" : "binary";
2570 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2574 adios ("fork", "unable to");
2578 close (fileno (ce
->ce_fp
));
2580 fprintf (stderr
, "unable to exec ");
2586 if (pidXwait (child_id
, NULL
)) {
2587 username
= password
= NULL
;
2597 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2602 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2603 if ((fp
= fopen (cachefile
, "w"))) {
2605 FILE *gp
= ce
->ce_fp
;
2607 fseek (gp
, 0L, SEEK_SET
);
2609 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2611 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2612 advise ("openFTP", "fwrite");
2617 admonish (ce
->ce_file
, "error reading");
2618 (void) m_unlink (cachefile
);
2622 admonish (cachefile
, "error writing");
2623 (void) m_unlink (cachefile
);
2631 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2632 *file
= ce
->ce_file
;
2633 return fileno (ce
->ce_fp
);
2644 return init_encoding (ct
, openMail
);
2649 openMail (CT ct
, char **file
)
2651 int child_id
, fd
, i
, vecp
;
2653 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2654 struct exbody
*e
= ct
->c_ctexbody
;
2655 CE ce
= &ct
->c_cefile
;
2657 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2668 if (!e
->eb_server
) {
2669 content_error (NULL
, ct
, "missing server parameter");
2673 /* Get buffer ready to go */
2675 buflen
= sizeof(buffer
);
2677 /* Now, construct query message */
2678 snprintf (bp
, buflen
, "Retrieve content");
2684 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2690 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2692 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2694 /* Now, check answer */
2695 if (!read_yes_or_no_if_tty (buffer
))
2699 vec
[vecp
++] = r1bindex (mailproc
, '/');
2700 vec
[vecp
++] = e
->eb_server
;
2701 vec
[vecp
++] = "-subject";
2702 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2703 vec
[vecp
++] = "-body";
2704 vec
[vecp
++] = e
->eb_body
;
2707 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2711 advise ("fork", "unable to");
2715 execvp (mailproc
, vec
);
2716 fprintf (stderr
, "unable to exec ");
2722 if (pidXwait (child_id
, NULL
) == OK
)
2723 advise (NULL
, "request sent");
2727 if (*file
== NULL
) {
2729 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2730 adios(NULL
, "unable to create temporary file in %s",
2733 ce
->ce_file
= add (tempfile
, NULL
);
2736 ce
->ce_file
= add (*file
, NULL
);
2740 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2741 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2745 /* showproc is for mhshow and mhstore, though mhlist -debug
2746 * prints it, too. */
2748 free (ct
->c_showproc
);
2749 ct
->c_showproc
= add ("true", NULL
);
2751 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2752 *file
= ce
->ce_file
;
2753 return fileno (ce
->ce_fp
);
2764 return init_encoding (ct
, openURL
);
2769 openURL (CT ct
, char **file
)
2771 struct exbody
*e
= ct
->c_ctexbody
;
2772 CE ce
= &ct
->c_cefile
;
2773 char *urlprog
, *program
;
2774 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2775 int fd
, caching
, cachetype
;
2776 struct msgs_array args
= { 0, 0, NULL
};
2779 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2783 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2787 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2799 content_error(NULL
, ct
, "missing url parameter");
2803 ce
->ce_unlink
= (*file
== NULL
);
2805 cachefile
[0] = '\0';
2807 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2808 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2809 if (*file
== NULL
) {
2816 ce
->ce_file
= add(*file
, NULL
);
2818 ce
->ce_file
= add(cachefile
, NULL
);
2821 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2822 adios(NULL
, "unable to create temporary file in %s",
2825 ce
->ce_file
= add (tempfile
, NULL
);
2828 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2829 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2833 switch (child_id
= fork()) {
2835 adios ("fork", "unable to");
2839 argsplit_msgarg(&args
, urlprog
, &program
);
2840 app_msgarg(&args
, e
->eb_url
);
2841 app_msgarg(&args
, NULL
);
2842 dup2(fileno(ce
->ce_fp
), 1);
2843 close(fileno(ce
->ce_fp
));
2844 execvp(program
, args
.msgs
);
2845 fprintf(stderr
, "Unable to exec ");
2851 if (pidXwait(child_id
, NULL
)) {
2859 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2864 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2865 if ((fp
= fopen(cachefile
, "w"))) {
2867 FILE *gp
= ce
->ce_fp
;
2869 fseeko(gp
, 0, SEEK_SET
);
2871 while ((cc
= fread(buffer
, sizeof(*buffer
),
2872 sizeof(buffer
), gp
)) > 0)
2873 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2874 advise ("openURL", "fwrite");
2880 admonish(ce
->ce_file
, "error reading");
2881 (void) m_unlink (cachefile
);
2888 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2889 *file
= ce
->ce_file
;
2895 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2896 * has to be base64 decoded.
2899 readDigest (CT ct
, char *cp
)
2901 unsigned char *digest
;
2904 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2905 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2907 if (strlen ((char *) digest
) <= maxlen
) {
2908 memcpy (ct
->c_digest
, digest
, maxlen
);
2913 fprintf (stderr
, "MD5 digest=");
2914 for (i
= 0; i
< maxlen
; ++i
) {
2915 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2917 fprintf (stderr
, "\n");
2923 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2924 (int) strlen ((char *) digest
));
2934 /* Multipart parts might have content before the first subpart and/or
2935 after the last subpart that hasn't been stored anywhere else, so do
2938 get_leftover_mp_content (CT ct
, int before
/* or after */)
2940 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2942 int found_boundary
= 0;
2948 char *content
= NULL
;
2950 if (! m
) return NOTOK
;
2953 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2955 /* Isolate the beginning of this part to the beginning of the
2956 first subpart and save any content between them. */
2957 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2958 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2959 boundary
= concat ("--", m
->mp_start
, NULL
);
2961 struct part
*last_subpart
= NULL
;
2962 struct part
*subpart
;
2964 /* Go to the last subpart to get its end position. */
2965 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2966 last_subpart
= subpart
;
2969 if (last_subpart
== NULL
) return NOTOK
;
2971 /* Isolate the end of the last subpart to the end of this part
2972 and save any content between them. */
2973 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2974 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2975 boundary
= concat ("--", m
->mp_stop
, NULL
);
2978 /* Back up by 1 to pick up the newline. */
2979 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2981 /* Don't look beyond beginning of first subpart (before) or
2982 next part (after). */
2983 if (read
> max
) bufp
[read
-max
] = '\0';
2986 if (! strcmp (bufp
, boundary
)) {
2990 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2996 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2998 char *old_content
= content
;
2999 content
= concat (content
, bufp
, NULL
);
3003 ? concat ("\n", bufp
, NULL
)
3004 : concat (bufp
, NULL
);
3009 if (found_boundary
|| read
> max
) break;
3011 if (read
> max
) break;
3015 /* Skip the newline if that's all there is. */
3019 /* Remove trailing newline, except at EOF. */
3020 if ((before
|| ! feof (ct
->c_fp
)) &&
3021 (cp
= content
+ strlen (content
)) > content
&&
3026 if (strlen (content
) > 1) {
3028 m
->mp_content_before
= content
;
3030 m
->mp_content_after
= content
;
3045 ct_type_str (int type
) {
3047 case CT_APPLICATION
:
3048 return "application";
3064 return "unknown_type";
3070 ct_subtype_str (int type
, int subtype
) {
3072 case CT_APPLICATION
:
3074 case APPLICATION_OCTETS
:
3076 case APPLICATION_POSTSCRIPT
:
3077 return "postscript";
3079 return "unknown_app_subtype";
3083 case MESSAGE_RFC822
:
3085 case MESSAGE_PARTIAL
:
3087 case MESSAGE_EXTERNAL
:
3090 return "unknown_msg_subtype";
3096 case MULTI_ALTERNATE
:
3097 return "alternative";
3100 case MULTI_PARALLEL
:
3105 return "unknown_multipart_subtype";
3116 return "unknown_text_subtype";
3119 return "unknown_type";
3125 ct_str_type (const char *type
) {
3126 struct str2init
*s2i
;
3128 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3129 if (! strcasecmp (type
, s2i
->si_key
)) {
3133 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3142 ct_str_subtype (int type
, const char *subtype
) {
3146 case CT_APPLICATION
:
3147 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3148 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3152 return kv
->kv_value
;
3154 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3155 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3159 return kv
->kv_value
;
3161 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3162 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3166 return kv
->kv_value
;
3168 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3169 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3173 return kv
->kv_value
;
3180 /* Find the content type and InitFunc for the CT. */
3181 const struct str2init
*
3182 get_ct_init (int type
) {
3183 const struct str2init
*sp
;
3185 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3186 if (type
== sp
->si_val
) {
3195 ce_str (int encoding
) {
3200 return "quoted-printable";
3216 /* Find the content type and InitFunc for the content encoding method. */
3217 const struct str2init
*
3218 get_ce_method (const char *method
) {
3219 struct str2init
*sp
;
3221 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3222 if (! strcasecmp (method
, sp
->si_key
)) {
3231 * Parse a series of MIME attributes (or parameters) given a header as
3234 * Arguments include:
3236 * filename - Name of input file (for error messages)
3237 * fieldname - Name of field being processed
3238 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3239 * Updated to point to end of attributes when finished.
3240 * param_head - Pointer to head of parameter list
3241 * param_tail - Pointer to tail of parameter list
3242 * commentp - Pointer to header comment pointer (may be NULL)
3244 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3245 * DONE to indicate a benign error (minor parsing error, but the program
3250 parse_header_attrs (const char *filename
, const char *fieldname
,
3251 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3254 char *cp
= *header_attrp
;
3260 struct sectlist
*next
;
3266 struct sectlist
*sechead
;
3267 struct parmlist
*next
;
3268 } *pp
, *pp2
, *phead
= NULL
;
3270 while (*cp
== ';') {
3271 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3272 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3275 while (isspace ((unsigned char) *cp
))
3279 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3284 if (! suppress_extraneous_trailing_semicolon_warning
) {
3286 "extraneous trailing ';' in message %s's %s: "
3288 filename
, fieldname
);
3290 extraneous_trailing_semicolon
= 1;
3294 /* down case the attribute name */
3295 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3296 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
3297 *dp
= tolower ((unsigned char) *dp
);
3299 for (up
= dp
; isspace ((unsigned char) *dp
);)
3301 if (dp
== cp
|| *dp
!= '=') {
3303 "invalid parameter in message %s's %s: "
3304 "field\n%*sparameter %s (error detected at offset %d)",
3305 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3310 * To handle RFC 2231, we have to deal with the following extensions:
3312 * name*=encoded-value
3313 * name*<N>=part-N-of-a-parameter-value
3314 * name*<N>*=encoded-part-N-of-a-parameter-value
3317 * If there's a * right before the equal sign, it's encoded.
3318 * If there's a * and one or more digits, then it's section N.
3320 * Remember we can have one or the other, or both. cp points to
3321 * beginning of name, up points past the last character in the
3325 for (vp
= cp
; vp
< up
; vp
++) {
3326 if (*vp
== '*' && vp
< up
- 1) {
3329 } else if (*vp
== '*' && vp
== up
- 1) {
3331 } else if (partial
) {
3332 if (isdigit((unsigned char) *vp
))
3333 index
= *vp
- '0' + index
* 10;
3335 advise (NULL
, "invalid parameter index in message %s's "
3336 "%s: field\n%*s(parameter %s)", filename
,
3337 fieldname
, strlen(invo_name
) + 2, "", cp
);
3346 * Break out the parameter name and value sections and allocate
3350 nameptr
= mh_xmalloc(len
+ 1);
3351 strncpy(nameptr
, cp
, len
);
3352 nameptr
[len
] = '\0';
3354 for (dp
++; isspace ((unsigned char) *dp
);)
3359 * Single quotes delimit the character set and language tag.
3360 * They are required on the first section (or a complete
3365 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3371 charset
= mh_xmalloc(len
+ 1);
3372 strncpy(charset
, dp
, len
);
3373 charset
[len
] = '\0';
3379 advise(NULL
, "missing charset in message %s's %s: "
3380 "field\n%*s(parameter %s)", filename
, fieldname
,
3381 strlen(invo_name
) + 2, "", nameptr
);
3387 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3394 lang
= mh_xmalloc(len
+ 1);
3395 strncpy(lang
, dp
, len
);
3402 advise(NULL
, "missing language tag in message %s's %s: "
3403 "field\n%*s(parameter %s)", filename
, fieldname
,
3404 strlen(invo_name
) + 2, "", nameptr
);
3415 * At this point vp should be pointing at the beginning
3416 * of the encoded value/section. Continue until we reach
3417 * the end or get whitespace. But first, calculate the
3418 * length so we can allocate the correct buffer size.
3421 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3423 if (*(vp
+ 1) == '\0' ||
3424 !isxdigit((unsigned char) *(vp
+ 1)) ||
3425 *(vp
+ 2) == '\0' ||
3426 !isxdigit((unsigned char) *(vp
+ 2))) {
3427 advise(NULL
, "invalid encoded sequence in message "
3428 "%s's %s: field\n%*s(parameter %s)",
3429 filename
, fieldname
, strlen(invo_name
) + 2,
3443 up
= valptr
= mh_xmalloc(len
+ 1);
3445 for (vp
= dp
; istoken(*vp
); vp
++) {
3447 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3458 * A "normal" string. If it's got a leading quote, then we
3459 * strip the quotes out. Otherwise go until we reach the end
3460 * or get whitespace. Note we scan it twice; once to get the
3461 * length, then the second time copies it into the destination
3468 for (cp
= dp
+ 1;;) {
3473 "invalid quoted-string in message %s's %s: "
3474 "field\n%*s(parameter %s)",
3475 filename
, fieldname
, strlen(invo_name
) + 2, "",
3498 for (cp
= dp
; istoken (*cp
); cp
++) {
3503 valptr
= mh_xmalloc(len
+ 1);
3507 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3515 strncpy(valptr
, cp
= dp
, len
);
3523 * If 'partial' is set, we don't allocate a parameter now. We
3524 * put it on the parameter linked list to be reassembled later.
3526 * "phead" points to a list of all parameters we need to reassemble.
3527 * Each parameter has a list of sections. We insert the sections in
3532 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3533 if (strcasecmp(nameptr
, pp
->name
) == 0)
3545 * Insert this into the section linked list
3553 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3554 sp
->next
= pp
->sechead
;
3557 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3558 if (sp2
->index
== sp
->index
) {
3559 advise (NULL
, "duplicate index (%d) in message "
3560 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3561 filename
, fieldname
, strlen(invo_name
) + 2, "",
3566 if (sp2
->index
< sp
->index
&&
3567 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3568 sp
->next
= sp2
->next
;
3575 advise(NULL
, "Internal error: cannot insert partial "
3576 "param in message %s's %s: field\n%*s(parameter %s)",
3577 filename
, fieldname
, strlen(invo_name
) + 2, "",
3585 * Save our charset and lang tags.
3588 if (index
== 0 && encoded
) {
3591 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 advise(NULL
, "missing section %d for parameter in "
3622 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3623 filename
, fieldname
, strlen(invo_name
) + 2, "",
3630 p
= q
= mh_xmalloc(tlen
+ 1);
3631 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3632 memcpy(q
, sp
->value
, sp
->len
);
3642 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3643 pm
->pm_charset
= pp
->charset
;
3644 pm
->pm_lang
= pp
->lang
;
3655 * Return the charset for a particular content type.
3659 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 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3698 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3701 * Loop until we get a parameter that fits within a line. We
3702 * assume new lines start with a tab, so check our overflow based
3712 * At this point we're definitely continuing the line, so
3713 * be sure to include the parameter name and section index.
3716 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3717 params
->pm_name
, index
);
3720 * Both of these functions do a NUL termination
3724 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3725 numchars
, valoff
, index
);
3727 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3738 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3743 * "line" starts with a ;\n\t, so that doesn't count against
3744 * the length. But add 8 since it starts with a tab; that's
3745 * how we end up with 5.
3748 initialwidth
= strlen(line
) + 5;
3751 * At this point the line should be built, so add it to our
3752 * current output buffer.
3755 paramout
= add(line
, paramout
);
3759 * If this won't fit on the line, start a new one. Save room in
3760 * case we need a semicolon on the end
3763 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3775 * At this point, we're either finishing a contined parameter, or
3776 * we're working on a new one.
3780 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3781 params
->pm_name
, index
);
3783 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3788 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3789 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3791 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3792 strlen(params
->pm_value
+ valoff
), valoff
);
3800 paramout
= add(line
, paramout
);
3801 initialwidth
+= strlen(line
);
3803 params
= params
->pm_next
;
3807 *offsetout
= initialwidth
;
3813 * Calculate the size of a parameter.
3817 * pm - The parameter being output
3818 * index - If continuing the parameter, the index of the section
3820 * valueoff - The current offset into the parameter value that we're
3821 * working on (previous sections have consumed valueoff bytes).
3822 * encode - Set if we should perform encoding on this parameter section
3823 * (given that we're consuming bytesfit bytes).
3824 * cont - Set if the remaining data in value will not fit on a single
3825 * line and will need to be continued.
3826 * bytesfit - The number of bytes that we can consume from the parameter
3827 * value and still fit on a completely new line. The
3828 * calculation assumes the new line starts with a tab,
3829 * includes the parameter name and any encoding, and fits
3830 * within CPERLIN bytes. Will always be at least 1.
3834 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3837 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3838 size_t len
= 0, fit
= 0;
3839 int fitlimit
= 0, eightbit
, maxfit
;
3844 * Add up the length. First, start with the parameter name.
3847 len
= strlen(pm
->pm_name
);
3850 * Scan the parameter value and see if we need to do encoding for this
3854 eightbit
= contains8bit(start
, NULL
);
3857 * Determine if we need to encode this section. Encoding is necessary if:
3859 * - There are any 8-bit characters at all and we're on the first
3861 * - There are 8-bit characters within N bytes of our section start.
3862 * N is calculated based on the number of bytes it would take to
3863 * reach CPERLIN. Specifically:
3864 * 8 (starting tab) +
3865 * strlen(param name) +
3866 * 4 ('* for section marker, '=', opening/closing '"')
3868 * is the number of bytes used by everything that isn't part of the
3869 * value. So that gets subtracted from CPERLIN.
3872 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3873 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3874 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3878 len
++; /* Add in equal sign */
3882 * We're using maxfit as a marker for how many characters we can
3883 * fit into the line. Bump it by two because we're not using quotes
3890 * If we don't have a charset or language tag in this parameter,
3894 if (! pm
->pm_charset
) {
3895 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3896 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3897 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3898 "local character set is US-ASCII", pm
->pm_name
);
3901 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3903 len
++; /* For the encoding marker */
3906 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3911 * We know we definitely need to include an index. maxfit already
3912 * includes the section marker.
3914 len
+= strlen(indexchar
);
3916 for (p
= start
; *p
!= '\0'; p
++) {
3917 if (isparamencode(*p
)) {
3925 * Just so there's no confusion: maxfit is counting OUTPUT
3926 * characters (post-encoding). fit is counting INPUT characters.
3928 if (! fitlimit
&& maxfit
>= 0)
3930 else if (! fitlimit
)
3935 * Calculate the string length, but add room for quoting \
3936 * and " if necessary. Also account for quotes at beginning
3939 for (p
= start
; *p
!= '\0'; p
++) {
3950 if (! fitlimit
&& maxfit
>= 0)
3952 else if (! fitlimit
)
3969 * Output an encoded parameter string.
3973 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3974 size_t valueoff
, int index
)
3976 size_t outlen
= 0, n
;
3977 char *endptr
= output
+ len
, *p
;
3980 * First, output the marker for an encoded string.
3988 * If the index is 0, output the character set and language tag.
3989 * If theses were NULL, they should have already been filled in
3994 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3998 if (output
> endptr
) {
3999 advise(NULL
, "Internal error: parameter buffer overflow");
4005 * Copy over the value, encoding if necessary
4008 p
= pm
->pm_value
+ valueoff
;
4009 while (valuelen
-- > 0) {
4010 if (isparamencode(*p
)) {
4011 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4018 if (output
> endptr
) {
4019 advise(NULL
, "Internal error: parameter buffer overflow");
4030 * Output a "normal" parameter, without encoding. Be sure to escape
4031 * quotes and backslashes if necessary.
4035 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4039 char *endptr
= output
+ len
, *p
;
4045 p
= pm
->pm_value
+ valueoff
;
4047 while (valuelen
-- > 0) {
4057 if (output
> endptr
) {
4058 advise(NULL
, "Internal error: parameter buffer overflow");
4063 if (output
- 2 > endptr
) {
4064 advise(NULL
, "Internal error: parameter buffer overflow");
4075 * Add a parameter to the parameter linked list
4079 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4084 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4085 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4088 (*last
)->pm_next
= pm
;
4099 * Either replace a current parameter with a new value, or add the parameter
4100 * to the parameter linked list.
4104 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4108 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4109 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4111 * If nocopy is set, it's assumed that we own both name
4112 * and value. We don't need name, so we discard it now.
4117 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4122 return add_param(first
, last
, name
, value
, nocopy
);
4126 * Retrieve a parameter value from a parameter linked list. If the parameter
4127 * value needs converted to the local character set, do that now.
4131 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4133 while (first
!= NULL
) {
4134 if (strcasecmp(name
, first
->pm_name
) == 0) {
4136 return first
->pm_value
;
4137 return getcpy(get_param_value(first
, replace
));
4139 first
= first
->pm_next
;
4146 * Return a parameter value, converting to the local character set if
4150 char *get_param_value(PM pm
, char replace
)
4152 static char buffer
[4096]; /* I hope no parameters are larger */
4153 size_t bufsize
= sizeof(buffer
);
4158 ICONV_CONST
char *p
;
4159 #else /* HAVE_ICONV */
4161 #endif /* HAVE_ICONV */
4166 * If we don't have a character set indicated, it's assumed to be
4167 * US-ASCII. If it matches our character set, we don't need to convert
4171 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4172 strlen(pm
->pm_charset
))) {
4173 return pm
->pm_value
;
4177 * In this case, we need to convert. If we have iconv support, use
4178 * that. Otherwise, go through and simply replace every non-ASCII
4179 * character with the substitution character.
4184 bufsize
= sizeof(buffer
);
4185 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4187 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4188 if (cd
== (iconv_t
) -1) {
4192 inbytes
= strlen(pm
->pm_value
);
4196 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4197 if (errno
!= EILSEQ
) {
4202 * Reset shift state, substitute our character,
4203 * try to restart conversion.
4206 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4219 for (++p
, --inbytes
;
4220 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4239 #endif /* HAVE_ICONV */
4242 * Take everything non-ASCII and substituite the replacement character
4246 bufsize
= sizeof(buffer
);
4247 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4248 if (isascii((unsigned char) *p
) && !iscntrl((unsigned char) *p
))