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 if (name
[0] == ':') {
334 /* Special case: no blank line between header and body. The
335 file position indicator is on the newline at the end of the
336 line, but it needs to be one prior to the beginning of the
337 line. So subtract the length of the line, bufsz, plus 1. */
338 ct
->c_begin
= ftell (in
) - (bufsz
+ 1);
340 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
345 ct
->c_begin
= ftell (in
);
350 adios (NULL
, "message format error in component #%d", compnum
);
353 adios (NULL
, "getfld() returned %d", state
);
356 /* break out of the loop */
359 m_getfld_state_destroy (&gstate
);
362 * Read the content headers. We will parse the
363 * MIME related header fields into their various
364 * structures and set internal flags related to
365 * content type/subtype, etc.
368 hp
= ct
->c_first_hf
; /* start at first header field */
370 /* Get MIME-Version field */
371 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
376 vrsn
= add (hp
->value
, NULL
);
378 /* Now, cleanup this field */
381 while (isspace ((unsigned char) *cp
))
383 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
385 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
386 if (!isspace ((unsigned char) *dp
))
390 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
393 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
396 for (dp
= cp
; istoken (*dp
); dp
++)
400 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
403 admonish (NULL
, "message %s has unknown value for %s: field (%s)",
404 ct
->c_file
, VRSN_FIELD
, cp
);
409 if (! suppress_multiple_mime_version_warning
)
410 advise (NULL
, "message %s has multiple %s: fields",
411 ct
->c_file
, VRSN_FIELD
);
415 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
416 /* Get Content-Type field */
417 struct str2init
*s2i
;
418 CI ci
= &ct
->c_ctinfo
;
420 /* Check if we've already seen a Content-Type header */
422 advise (NULL
, "message %s has multiple %s: fields",
423 ct
->c_file
, TYPE_FIELD
);
427 /* Parse the Content-Type field */
428 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
432 * Set the Init function and the internal
433 * flag for this content type.
435 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
436 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
438 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
440 ct
->c_type
= s2i
->si_val
;
441 ct
->c_ctinitfnx
= s2i
->si_init
;
443 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
444 /* Get Content-Transfer-Encoding field */
446 struct str2init
*s2i
;
449 * Check if we've already seen the
450 * Content-Transfer-Encoding field
453 advise (NULL
, "message %s has multiple %s: fields",
454 ct
->c_file
, ENCODING_FIELD
);
458 /* get copy of this field */
459 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
461 while (isspace ((unsigned char) *cp
))
463 for (dp
= cp
; istoken (*dp
); dp
++)
469 * Find the internal flag and Init function
470 * for this transfer encoding.
472 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
473 if (!strcasecmp (cp
, s2i
->si_key
))
475 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
478 ct
->c_encoding
= s2i
->si_val
;
480 /* Call the Init function for this encoding */
481 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
484 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
485 /* Get Content-MD5 field */
491 if (ct
->c_digested
) {
492 advise (NULL
, "message %s has multiple %s: fields",
493 ct
->c_file
, MD5_FIELD
);
497 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
499 while (isspace ((unsigned char) *cp
))
501 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
503 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
504 if (!isspace ((unsigned char) *dp
))
508 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
511 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
516 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
524 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
525 /* Get Content-ID field */
526 ct
->c_id
= add (hp
->value
, ct
->c_id
);
528 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
529 /* Get Content-Description field */
530 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
532 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
533 /* Get Content-Disposition field */
534 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
539 hp
= hp
->next
; /* next header field */
543 * Check if we saw a Content-Type field.
544 * If not, then assign a default value for
545 * it, and the Init function.
549 * If we are inside a multipart/digest message,
550 * so default type is message/rfc822
553 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
555 ct
->c_type
= CT_MESSAGE
;
556 ct
->c_ctinitfnx
= InitMessage
;
559 * Else default type is text/plain
561 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
563 ct
->c_type
= CT_TEXT
;
564 ct
->c_ctinitfnx
= InitText
;
568 /* Use default Transfer-Encoding, if necessary */
570 ct
->c_encoding
= CE_7BIT
;
583 * small routine to add header field to list
587 add_header (CT ct
, char *name
, char *value
)
591 /* allocate header field structure */
594 /* link data into header structure */
599 /* link header structure into the list */
600 if (ct
->c_first_hf
== NULL
) {
601 ct
->c_first_hf
= hp
; /* this is the first */
604 ct
->c_last_hf
->next
= hp
; /* add it to the end */
613 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
614 * directives. Fills in the information of the CTinfo structure.
617 get_ctinfo (char *cp
, CT ct
, int magic
)
626 /* store copy of Content-Type line */
627 cp
= ct
->c_ctline
= add (cp
, NULL
);
629 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
632 /* change newlines to spaces */
633 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
636 /* trim trailing spaces */
637 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
638 if (!isspace ((unsigned char) *dp
))
643 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
645 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
646 &ci
->ci_comment
) == NOTOK
)
649 for (dp
= cp
; istoken (*dp
); dp
++)
652 ci
->ci_type
= add (cp
, NULL
); /* store content type */
656 advise (NULL
, "invalid %s: field in message %s (empty type)",
657 TYPE_FIELD
, ct
->c_file
);
660 ToLower(ci
->ci_type
);
662 while (isspace ((unsigned char) *cp
))
665 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
666 &ci
->ci_comment
) == NOTOK
)
671 ci
->ci_subtype
= add ("", NULL
);
676 while (isspace ((unsigned char) *cp
))
679 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
680 &ci
->ci_comment
) == NOTOK
)
683 for (dp
= cp
; istoken (*dp
); dp
++)
686 ci
->ci_subtype
= add (cp
, NULL
); /* store the content subtype */
689 if (!*ci
->ci_subtype
) {
691 "invalid %s: field in message %s (empty subtype for \"%s\")",
692 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
695 ToLower(ci
->ci_subtype
);
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
== '<') {
717 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
718 advise (NULL
, "invalid ID in message %s", ct
->c_file
);
724 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
730 while (isspace ((unsigned char) *cp
))
735 * Get any [Content-Description] given in buffer.
737 if (magic
&& *cp
== '[') {
739 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
743 advise (NULL
, "invalid description in message %s", ct
->c_file
);
751 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
757 while (isspace ((unsigned char) *cp
))
762 * Get any {Content-Disposition} given in buffer.
764 if (magic
&& *cp
== '{') {
766 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
770 advise (NULL
, "invalid disposition in message %s", ct
->c_file
);
778 if (get_dispo(cp
, ct
, 1) != OK
)
784 while (isspace ((unsigned char) *cp
))
789 * Get any extension directives (right now just the content transfer
790 * encoding, but maybe others) that we care about.
793 if (magic
&& *cp
== '*') {
795 * See if it's a CTE we match on
800 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
804 advise (NULL
, "invalid null transfer encoding specification");
811 ct
->c_reqencoding
= CE_UNKNOWN
;
813 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
814 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
815 ct
->c_reqencoding
= kv
->kv_value
;
820 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
821 advise (NULL
, "invalid CTE specification: \"%s\"", dp
);
825 while (isspace ((unsigned char) *cp
))
830 * Check if anything is left over
834 ci
->ci_magic
= add (cp
, NULL
);
836 /* If there is a Content-Disposition header and it doesn't
837 have a *filename=, extract it from the magic contents.
838 The r1bindex call skips any leading directory
840 if (ct
->c_dispo_type
&&
841 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
842 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
843 r1bindex(ci
->ci_magic
, '/'), 0);
848 "extraneous information in message %s's %s: field\n%*s(%s)",
849 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
857 * Parse out a Content-Disposition header. A lot of this is cribbed from
861 get_dispo (char *cp
, CT ct
, int buildflag
)
863 char *dp
, *dispoheader
;
868 * Save the whole copy of the Content-Disposition header, unless we're
869 * processing a mhbuild directive. A NULL c_dispo will be a flag to
870 * mhbuild that the disposition header needs to be generated at that
874 dispoheader
= cp
= add(cp
, NULL
);
876 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
879 /* change newlines to spaces */
880 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
883 /* trim trailing spaces */
884 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
885 if (!isspace ((unsigned char) *dp
))
890 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
892 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
898 for (dp
= cp
; istoken (*dp
); dp
++)
901 ct
->c_dispo_type
= add (cp
, NULL
); /* store disposition type */
904 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
907 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
908 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
910 if (status
== NOTOK
) {
916 "extraneous information in message %s's %s: field\n%*s(%s)",
917 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
923 ct
->c_dispo
= dispoheader
;
930 get_comment (const char *filename
, const char *fieldname
, char **ap
,
935 char c
, buffer
[BUFSIZ
], *dp
;
945 advise (NULL
, "invalid comment in message %s's %s: field",
946 filename
, fieldname
);
951 if ((c
= *cp
++) == '\0')
974 if ((dp
= *commentp
)) {
975 *commentp
= concat (dp
, " ", buffer
, NULL
);
978 *commentp
= add (buffer
, NULL
);
982 while (isspace ((unsigned char) *cp
))
993 * Handles content types audio, image, and video.
994 * There's not much to do right here.
1002 return OK
; /* not much to do here */
1013 char buffer
[BUFSIZ
];
1018 CI ci
= &ct
->c_ctinfo
;
1020 /* check for missing subtype */
1021 if (!*ci
->ci_subtype
)
1022 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1025 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1027 /* allocate text character set structure */
1029 ct
->c_ctparams
= (void *) t
;
1031 /* scan for charset parameter */
1032 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1033 if (!strcasecmp (pm
->pm_name
, "charset"))
1036 /* check if content specified a character set */
1038 chset
= pm
->pm_value
;
1039 t
->tx_charset
= CHARSET_SPECIFIED
;
1041 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1045 * If we can not handle character set natively,
1046 * then check profile for string to modify the
1047 * terminal or display method.
1049 * termproc is for mhshow, though mhlist -debug prints it, too.
1051 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1052 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1053 if ((cp
= context_find (buffer
)))
1054 ct
->c_termproc
= mh_xstrdup(cp
);
1066 InitMultiPart (CT ct
)
1076 struct multipart
*m
;
1077 struct part
*part
, **next
;
1078 CI ci
= &ct
->c_ctinfo
;
1083 * The encoding for multipart messages must be either
1084 * 7bit, 8bit, or binary (per RFC2045).
1086 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1087 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1088 /* Copy the Content-Transfer-Encoding header field body so we can
1089 remove any trailing whitespace and leading blanks from it. */
1090 char *cte
= add (ct
->c_celine
? ct
->c_celine
: "(null)", NULL
);
1092 bp
= cte
+ strlen (cte
) - 1;
1093 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1094 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1097 "\"%s/%s\" type in message %s must be encoded in\n"
1098 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1099 "mhfixmsg -fixcte can fix it, or\n"
1100 "manually edit the file and change the \"%s\"\n"
1101 "Content-Transfer-Encoding to one of those. For now",
1102 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1109 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1112 * Check for "boundary" parameter, which is
1113 * required for multipart messages.
1116 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1117 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1123 /* complain if boundary parameter is missing */
1126 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1127 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1131 /* allocate primary structure for multipart info */
1133 ct
->c_ctparams
= (void *) m
;
1135 /* check if boundary parameter contains only whitespace characters */
1136 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1139 advise (NULL
, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1140 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1144 /* remove trailing whitespace from boundary parameter */
1145 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1146 if (!isspace ((unsigned char) *dp
))
1150 /* record boundary separators */
1151 m
->mp_start
= concat (bp
, "\n", NULL
);
1152 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1154 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1155 advise (ct
->c_file
, "unable to open for reading");
1159 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1161 next
= &m
->mp_parts
;
1165 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1170 if (bufp
[0] != '-' || bufp
[1] != '-')
1173 if (strcmp (bufp
+ 2, m
->mp_start
))
1178 next
= &part
->mp_next
;
1180 if (!(p
= get_content (fp
, ct
->c_file
,
1181 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1189 fseek (fp
, pos
, SEEK_SET
);
1192 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1196 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1197 if (p
->c_end
< p
->c_begin
)
1198 p
->c_begin
= p
->c_end
;
1203 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1208 if (! suppress_bogus_mp_content_warning
) {
1209 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1211 bogus_mp_content
= 1;
1213 if (!inout
&& part
) {
1215 p
->c_end
= ct
->c_end
;
1217 if (p
->c_begin
>= p
->c_end
) {
1218 for (next
= &m
->mp_parts
; *next
!= part
;
1219 next
= &((*next
)->mp_next
))
1228 /* reverse the order of the parts for multipart/alternative */
1229 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1235 * label all subparts with part number, and
1236 * then initialize the content of the subpart.
1241 char partnam
[BUFSIZ
];
1244 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1245 pp
= partnam
+ strlen (partnam
);
1250 for (part
= m
->mp_parts
, partnum
= 1; part
;
1251 part
= part
->mp_next
, partnum
++) {
1254 sprintf (pp
, "%d", partnum
);
1255 p
->c_partno
= add (partnam
, NULL
);
1257 /* initialize the content of the subparts */
1258 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1267 get_leftover_mp_content (ct
, 1);
1268 get_leftover_mp_content (ct
, 0);
1278 * reverse the order of the parts of a multipart/alternative,
1279 * presumably to put the "most favored" alternative first, for
1280 * ease of choosing/displaying it later on. from a mail message on
1281 * nmh-workers, from kenh:
1282 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1283 * see code in mhn that did the same thing... Acccording to the RCS
1284 * logs, that code was around from the initial checkin of mhn.c by
1285 * John Romine in 1992, which is as far back as we have."
1288 reverse_parts (CT ct
)
1290 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1294 /* Reverse the order of its parts by walking the mp_parts list
1295 and pushing each node to the front. */
1296 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1297 next
= part
->mp_next
;
1298 part
->mp_next
= m
->mp_parts
;
1304 move_preferred_part (CT ct
, char *type
, char *subtype
)
1306 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1307 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1311 /* move the matching part(s) to the head of the list: walk the
1312 * list of parts, move matching parts to a new list (maintaining
1313 * their order), and finally, concatenate the old list onto the
1320 head
->mp_next
= m
->mp_parts
;
1321 nhead
->mp_next
= NULL
;
1325 part
= head
->mp_next
;
1326 while (part
!= NULL
) {
1327 ci
= &part
->mp_part
->c_ctinfo
;
1328 if (!strcasecmp(ci
->ci_type
, type
) &&
1329 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1330 prev
->mp_next
= part
->mp_next
;
1331 part
->mp_next
= NULL
;
1332 ntail
->mp_next
= part
;
1334 part
= prev
->mp_next
;
1337 part
= prev
->mp_next
;
1340 ntail
->mp_next
= head
->mp_next
;
1341 m
->mp_parts
= nhead
->mp_next
;
1346 * move parts that match the user's preferences (-prefer) to the head
1347 * of the line. process preferences in reverse so first one given
1348 * ends up first in line
1354 for (i
= npreferred
-1; i
>= 0; i
--)
1355 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1360 /* parse_mime() arranges alternates in reverse (priority) order. This
1361 function can be used to reverse them back. This will put, for
1362 example, a text/plain part before a text/html part in a
1363 multipart/alternative part, for example, where it belongs. */
1365 reverse_alternative_parts (CT ct
) {
1366 if (ct
->c_type
== CT_MULTIPART
) {
1367 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1370 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1374 /* And call recursively on each part of a multipart. */
1375 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1376 reverse_alternative_parts (part
->mp_part
);
1389 CI ci
= &ct
->c_ctinfo
;
1391 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1393 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1394 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1398 /* check for missing subtype */
1399 if (!*ci
->ci_subtype
)
1400 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1403 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1405 switch (ct
->c_subtype
) {
1406 case MESSAGE_RFC822
:
1409 case MESSAGE_PARTIAL
:
1415 ct
->c_ctparams
= (void *) p
;
1417 /* scan for parameters "id", "number", and "total" */
1418 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1419 if (!strcasecmp (pm
->pm_name
, "id")) {
1420 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1423 if (!strcasecmp (pm
->pm_name
, "number")) {
1424 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1425 || p
->pm_partno
< 1) {
1428 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1429 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1430 ct
->c_file
, TYPE_FIELD
);
1435 if (!strcasecmp (pm
->pm_name
, "total")) {
1436 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1445 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1447 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1448 ci
->ci_type
, ci
->ci_subtype
,
1449 ct
->c_file
, TYPE_FIELD
);
1455 case MESSAGE_EXTERNAL
:
1463 ct
->c_ctparams
= (void *) e
;
1466 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1467 advise (ct
->c_file
, "unable to open for reading");
1471 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1473 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1481 p
->c_ceopenfnx
= NULL
;
1482 if ((exresult
= params_external (ct
, 0)) != NOTOK
1483 && p
->c_ceopenfnx
== openMail
) {
1487 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1489 content_error (NULL
, ct
,
1490 "empty body for access-type=mail-server");
1494 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1495 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1497 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1499 adios ("failed", "fread");
1502 adios (NULL
, "unexpected EOF from fread");
1505 bp
+= cc
, size
-= cc
;
1512 p
->c_end
= p
->c_begin
;
1517 if (exresult
== NOTOK
)
1519 if (e
->eb_flags
== NOTOK
)
1522 switch (p
->c_type
) {
1527 if (p
->c_subtype
!= MESSAGE_RFC822
)
1531 e
->eb_partno
= ct
->c_partno
;
1533 (*p
->c_ctinitfnx
) (p
);
1548 params_external (CT ct
, int composing
)
1551 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1552 CI ci
= &ct
->c_ctinfo
;
1554 ct
->c_ceopenfnx
= NULL
;
1555 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1556 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1557 struct str2init
*s2i
;
1558 CT p
= e
->eb_content
;
1560 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1561 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1564 e
->eb_access
= pm
->pm_value
;
1565 e
->eb_flags
= NOTOK
;
1566 p
->c_encoding
= CE_EXTERNAL
;
1569 e
->eb_access
= s2i
->si_key
;
1570 e
->eb_flags
= s2i
->si_val
;
1571 p
->c_encoding
= CE_EXTERNAL
;
1573 /* Call the Init function for this external type */
1574 if ((*s2i
->si_init
)(p
) == NOTOK
)
1578 if (!strcasecmp (pm
->pm_name
, "name")) {
1579 e
->eb_name
= pm
->pm_value
;
1582 if (!strcasecmp (pm
->pm_name
, "permission")) {
1583 e
->eb_permission
= pm
->pm_value
;
1586 if (!strcasecmp (pm
->pm_name
, "site")) {
1587 e
->eb_site
= pm
->pm_value
;
1590 if (!strcasecmp (pm
->pm_name
, "directory")) {
1591 e
->eb_dir
= pm
->pm_value
;
1594 if (!strcasecmp (pm
->pm_name
, "mode")) {
1595 e
->eb_mode
= pm
->pm_value
;
1598 if (!strcasecmp (pm
->pm_name
, "size")) {
1599 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1602 if (!strcasecmp (pm
->pm_name
, "server")) {
1603 e
->eb_server
= pm
->pm_value
;
1606 if (!strcasecmp (pm
->pm_name
, "subject")) {
1607 e
->eb_subject
= pm
->pm_value
;
1610 if (!strcasecmp (pm
->pm_name
, "url")) {
1612 * According to RFC 2017, we have to remove all whitespace from
1616 char *u
, *p
= pm
->pm_value
;
1617 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1619 for (; *p
!= '\0'; p
++) {
1620 if (! isspace((unsigned char) *p
))
1627 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1628 e
->eb_body
= getcpy (pm
->pm_value
);
1633 if (!e
->eb_access
) {
1635 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1636 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1649 InitApplication (CT ct
)
1651 CI ci
= &ct
->c_ctinfo
;
1654 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1661 * TRANSFER ENCODINGS
1665 init_encoding (CT ct
, OpenCEFunc openfnx
)
1667 ct
->c_ceopenfnx
= openfnx
;
1668 ct
->c_ceclosefnx
= close_encoding
;
1669 ct
->c_cesizefnx
= size_encoding
;
1676 close_encoding (CT ct
)
1678 CE ce
= &ct
->c_cefile
;
1687 static unsigned long
1688 size_encoding (CT ct
)
1693 CE ce
= &ct
->c_cefile
;
1696 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1697 return (long) st
.st_size
;
1700 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1701 return (long) st
.st_size
;
1705 if (ct
->c_encoding
== CE_EXTERNAL
)
1706 return (ct
->c_end
- ct
->c_begin
);
1709 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1710 return (ct
->c_end
- ct
->c_begin
);
1712 if (fstat (fd
, &st
) != NOTOK
)
1713 size
= (long) st
.st_size
;
1717 (*ct
->c_ceclosefnx
) (ct
);
1729 return init_encoding (ct
, openBase64
);
1734 openBase64 (CT ct
, char **file
)
1737 int fd
, own_ct_fp
= 0;
1738 char *cp
, *buffer
= NULL
;
1739 /* sbeck -- handle suffixes */
1741 CE ce
= &ct
->c_cefile
;
1742 unsigned char *decoded
;
1744 unsigned char digest
[16];
1747 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1752 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1753 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1759 if (*file
== NULL
) {
1762 ce
->ce_file
= add (*file
, NULL
);
1766 /* sbeck@cise.ufl.edu -- handle suffixes */
1768 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1769 if (ce
->ce_unlink
) {
1770 /* Create temporary file with filename extension. */
1771 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1772 adios(NULL
, "unable to create temporary file in %s",
1776 ce
->ce_file
= add (cp
, ce
->ce_file
);
1778 } else if (*file
== NULL
) {
1780 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1781 adios(NULL
, "unable to create temporary file in %s",
1784 ce
->ce_file
= add (tempfile
, NULL
);
1787 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1788 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1792 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1793 adios (NULL
, "internal error(1)");
1795 buffer
= mh_xmalloc (len
+ 1);
1798 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1799 content_error (ct
->c_file
, ct
, "unable to open for reading");
1805 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1808 switch (cc
= read (fd
, cp
, len
)) {
1810 content_error (ct
->c_file
, ct
, "error reading from");
1814 content_error (NULL
, ct
, "premature eof");
1825 /* decodeBase64() requires null-terminated input. */
1828 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1829 ct
->c_digested
? digest
: NULL
) == OK
) {
1831 unsigned char *decoded_p
= decoded
;
1832 for (i
= 0; i
< decoded_len
; ++i
) {
1833 putc (*decoded_p
++, ce
->ce_fp
);
1836 if (ferror (ce
->ce_fp
)) {
1837 content_error (ce
->ce_file
, ct
, "error writing to");
1841 if (ct
->c_digested
) {
1842 if (memcmp(digest
, ct
->c_digest
,
1843 sizeof(digest
) / sizeof(digest
[0]))) {
1844 content_error (NULL
, ct
,
1845 "content integrity suspect (digest mismatch) -- continuing");
1848 fprintf (stderr
, "content integrity confirmed\n");
1856 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1858 if (fflush (ce
->ce_fp
)) {
1859 content_error (ce
->ce_file
, ct
, "error writing to");
1863 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1866 *file
= ce
->ce_file
;
1872 return fileno (ce
->ce_fp
);
1879 free_encoding (ct
, 0);
1889 static char hex2nib
[0x80] = {
1890 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1891 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
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, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1897 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1898 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1899 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1900 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1901 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1902 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1912 return init_encoding (ct
, openQuoted
);
1917 openQuoted (CT ct
, char **file
)
1919 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1925 CE ce
= &ct
->c_cefile
;
1926 /* sbeck -- handle suffixes */
1931 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1936 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1937 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1943 if (*file
== NULL
) {
1946 ce
->ce_file
= add (*file
, NULL
);
1950 /* sbeck@cise.ufl.edu -- handle suffixes */
1952 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1953 if (ce
->ce_unlink
) {
1954 /* Create temporary file with filename extension. */
1955 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1956 adios(NULL
, "unable to create temporary file in %s",
1960 ce
->ce_file
= add (cp
, ce
->ce_file
);
1962 } else if (*file
== NULL
) {
1964 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1965 adios(NULL
, "unable to create temporary file in %s",
1968 ce
->ce_file
= add (tempfile
, NULL
);
1971 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1972 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1976 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1977 adios (NULL
, "internal error(2)");
1980 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1981 content_error (ct
->c_file
, ct
, "unable to open for reading");
1987 if ((digested
= ct
->c_digested
))
1988 MD5Init (&mdContext
);
1995 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1997 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
1998 content_error (NULL
, ct
, "premature eof");
2002 if ((cc
= gotlen
) > len
)
2006 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2007 if (!isspace ((unsigned char) *ep
))
2011 for (; cp
< ep
; cp
++) {
2013 /* in an escape sequence */
2015 /* at byte 1 of an escape sequence */
2016 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2017 /* next is byte 2 */
2020 /* at byte 2 of an escape sequence */
2022 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2023 putc (mask
, ce
->ce_fp
);
2025 MD5Update (&mdContext
, &mask
, 1);
2026 if (ferror (ce
->ce_fp
)) {
2027 content_error (ce
->ce_file
, ct
, "error writing to");
2030 /* finished escape sequence; next may be literal or a new
2031 * escape sequence */
2034 /* on to next byte */
2038 /* not in an escape sequence */
2040 /* starting an escape sequence, or invalid '='? */
2041 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2042 /* "=\n" soft line break, eat the \n */
2046 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2047 /* We don't have 2 bytes left, so this is an invalid
2048 * escape sequence; just show the raw bytes (below). */
2049 } else if (isxdigit ((unsigned char) cp
[1]) &&
2050 isxdigit ((unsigned char) cp
[2])) {
2051 /* Next 2 bytes are hex digits, making this a valid escape
2052 * sequence; let's decode it (above). */
2056 /* One or both of the next 2 is out of range, making this
2057 * an invalid escape sequence; just show the raw bytes
2062 /* Just show the raw byte. */
2063 putc (*cp
, ce
->ce_fp
);
2066 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2068 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2071 if (ferror (ce
->ce_fp
)) {
2072 content_error (ce
->ce_file
, ct
, "error writing to");
2078 content_error (NULL
, ct
,
2079 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2083 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2085 if (fflush (ce
->ce_fp
)) {
2086 content_error (ce
->ce_file
, ct
, "error writing to");
2091 unsigned char digest
[16];
2093 MD5Final (digest
, &mdContext
);
2094 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2095 sizeof(digest
) / sizeof(digest
[0])))
2096 content_error (NULL
, ct
,
2097 "content integrity suspect (digest mismatch) -- continuing");
2100 fprintf (stderr
, "content integrity confirmed\n");
2103 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2106 *file
= ce
->ce_file
;
2112 return fileno (ce
->ce_fp
);
2115 free_encoding (ct
, 0);
2132 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2135 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2141 open7Bit (CT ct
, char **file
)
2143 int cc
, fd
, len
, own_ct_fp
= 0;
2144 char buffer
[BUFSIZ
];
2145 /* sbeck -- handle suffixes */
2148 CE ce
= &ct
->c_cefile
;
2151 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2156 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2157 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2163 if (*file
== NULL
) {
2166 ce
->ce_file
= add (*file
, NULL
);
2170 /* sbeck@cise.ufl.edu -- handle suffixes */
2172 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2173 if (ce
->ce_unlink
) {
2174 /* Create temporary file with filename extension. */
2175 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2176 adios(NULL
, "unable to create temporary file in %s",
2180 ce
->ce_file
= add (cp
, ce
->ce_file
);
2182 } else if (*file
== NULL
) {
2184 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2185 adios(NULL
, "unable to create temporary file in %s",
2188 ce
->ce_file
= add (tempfile
, NULL
);
2191 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2192 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2196 if (ct
->c_type
== CT_MULTIPART
) {
2197 CI ci
= &ct
->c_ctinfo
;
2201 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2202 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2203 + 1 + strlen (ci
->ci_subtype
);
2204 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2207 fputs (buffer
, ce
->ce_fp
);
2211 if (ci
->ci_comment
) {
2212 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2213 fputs ("\n\t", ce
->ce_fp
);
2217 putc (' ', ce
->ce_fp
);
2220 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2223 fprintf (ce
->ce_fp
, "\n");
2225 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2227 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2229 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2230 fprintf (ce
->ce_fp
, "\n");
2233 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2234 adios (NULL
, "internal error(3)");
2237 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2238 content_error (ct
->c_file
, ct
, "unable to open for reading");
2244 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2246 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2248 content_error (ct
->c_file
, ct
, "error reading from");
2252 content_error (NULL
, ct
, "premature eof");
2260 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2261 advise ("open7Bit", "fwrite");
2263 if (ferror (ce
->ce_fp
)) {
2264 content_error (ce
->ce_file
, ct
, "error writing to");
2269 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2271 if (fflush (ce
->ce_fp
)) {
2272 content_error (ce
->ce_file
, ct
, "error writing to");
2276 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2279 *file
= ce
->ce_file
;
2284 return fileno (ce
->ce_fp
);
2287 free_encoding (ct
, 0);
2301 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2303 char cachefile
[BUFSIZ
];
2306 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2311 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2312 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2318 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2319 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2320 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2321 ce
->ce_file
= mh_xstrdup(cachefile
);
2325 admonish (cachefile
, "unable to fopen for reading");
2328 *fd
= fileno (ce
->ce_fp
);
2332 *file
= ce
->ce_file
;
2333 *fd
= fileno (ce
->ce_fp
);
2344 return init_encoding (ct
, openFile
);
2349 openFile (CT ct
, char **file
)
2352 char cachefile
[BUFSIZ
];
2353 struct exbody
*e
= ct
->c_ctexbody
;
2354 CE ce
= &ct
->c_cefile
;
2356 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2368 content_error (NULL
, ct
, "missing name parameter");
2372 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2375 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2376 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2380 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2381 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2382 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2386 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2387 if ((fp
= fopen (cachefile
, "w"))) {
2389 char buffer
[BUFSIZ
];
2390 FILE *gp
= ce
->ce_fp
;
2392 fseek (gp
, 0L, SEEK_SET
);
2394 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2396 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2397 advise ("openFile", "fwrite");
2402 admonish (ce
->ce_file
, "error reading");
2403 (void) m_unlink (cachefile
);
2407 admonish (cachefile
, "error writing");
2408 (void) m_unlink (cachefile
);
2415 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2416 *file
= ce
->ce_file
;
2417 return fileno (ce
->ce_fp
);
2427 return init_encoding (ct
, openFTP
);
2432 openFTP (CT ct
, char **file
)
2434 int cachetype
, caching
, fd
;
2436 char *bp
, *ftp
, *user
, *pass
;
2437 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2439 CE ce
= &ct
->c_cefile
;
2440 static char *username
= NULL
;
2441 static char *password
= NULL
;
2445 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2451 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2462 if (!e
->eb_name
|| !e
->eb_site
) {
2463 content_error (NULL
, ct
, "missing %s parameter",
2464 e
->eb_name
? "site": "name");
2468 /* Get the buffer ready to go */
2470 buflen
= sizeof(buffer
);
2473 * Construct the query message for user
2475 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2481 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2487 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2488 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2493 if (e
->eb_size
> 0) {
2494 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2499 snprintf (bp
, buflen
, "? ");
2502 * Now, check the answer
2504 if (!read_yes_or_no_if_tty (buffer
))
2509 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2513 ruserpass (e
->eb_site
, &username
, &password
, 0);
2518 ce
->ce_unlink
= (*file
== NULL
);
2520 cachefile
[0] = '\0';
2521 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2522 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2523 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2524 if (*file
== NULL
) {
2531 ce
->ce_file
= add (*file
, NULL
);
2533 ce
->ce_file
= add (cachefile
, NULL
);
2536 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2537 adios(NULL
, "unable to create temporary file in %s",
2540 ce
->ce_file
= add (tempfile
, NULL
);
2543 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2544 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2549 int child_id
, i
, vecp
;
2553 vec
[vecp
++] = r1bindex (ftp
, '/');
2554 vec
[vecp
++] = e
->eb_site
;
2557 vec
[vecp
++] = e
->eb_dir
;
2558 vec
[vecp
++] = e
->eb_name
;
2559 vec
[vecp
++] = ce
->ce_file
,
2560 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2561 ? "ascii" : "binary";
2566 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2570 adios ("fork", "unable to");
2574 close (fileno (ce
->ce_fp
));
2576 fprintf (stderr
, "unable to exec ");
2582 if (pidXwait (child_id
, NULL
)) {
2583 username
= password
= NULL
;
2593 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2598 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2599 if ((fp
= fopen (cachefile
, "w"))) {
2601 FILE *gp
= ce
->ce_fp
;
2603 fseek (gp
, 0L, SEEK_SET
);
2605 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2607 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2608 advise ("openFTP", "fwrite");
2613 admonish (ce
->ce_file
, "error reading");
2614 (void) m_unlink (cachefile
);
2618 admonish (cachefile
, "error writing");
2619 (void) m_unlink (cachefile
);
2627 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2628 *file
= ce
->ce_file
;
2629 return fileno (ce
->ce_fp
);
2640 return init_encoding (ct
, openMail
);
2645 openMail (CT ct
, char **file
)
2647 int child_id
, fd
, i
, vecp
;
2649 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2650 struct exbody
*e
= ct
->c_ctexbody
;
2651 CE ce
= &ct
->c_cefile
;
2653 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2664 if (!e
->eb_server
) {
2665 content_error (NULL
, ct
, "missing server parameter");
2669 /* Get buffer ready to go */
2671 buflen
= sizeof(buffer
);
2673 /* Now, construct query message */
2674 snprintf (bp
, buflen
, "Retrieve content");
2680 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2686 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2688 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2690 /* Now, check answer */
2691 if (!read_yes_or_no_if_tty (buffer
))
2695 vec
[vecp
++] = r1bindex (mailproc
, '/');
2696 vec
[vecp
++] = e
->eb_server
;
2697 vec
[vecp
++] = "-subject";
2698 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2699 vec
[vecp
++] = "-body";
2700 vec
[vecp
++] = e
->eb_body
;
2703 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2707 advise ("fork", "unable to");
2711 execvp (mailproc
, vec
);
2712 fprintf (stderr
, "unable to exec ");
2718 if (pidXwait (child_id
, NULL
) == OK
)
2719 advise (NULL
, "request sent");
2723 if (*file
== NULL
) {
2725 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2726 adios(NULL
, "unable to create temporary file in %s",
2729 ce
->ce_file
= add (tempfile
, NULL
);
2732 ce
->ce_file
= add (*file
, NULL
);
2736 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2737 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2741 /* showproc is for mhshow and mhstore, though mhlist -debug
2742 * prints it, too. */
2743 mh_xfree(ct
->c_showproc
);
2744 ct
->c_showproc
= add ("true", NULL
);
2746 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2747 *file
= ce
->ce_file
;
2748 return fileno (ce
->ce_fp
);
2759 return init_encoding (ct
, openURL
);
2764 openURL (CT ct
, char **file
)
2766 struct exbody
*e
= ct
->c_ctexbody
;
2767 CE ce
= &ct
->c_cefile
;
2768 char *urlprog
, *program
;
2769 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2770 int fd
, caching
, cachetype
;
2771 struct msgs_array args
= { 0, 0, NULL
};
2774 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2778 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2782 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2794 content_error(NULL
, ct
, "missing url parameter");
2798 ce
->ce_unlink
= (*file
== NULL
);
2800 cachefile
[0] = '\0';
2802 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2803 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2804 if (*file
== NULL
) {
2811 ce
->ce_file
= add(*file
, NULL
);
2813 ce
->ce_file
= add(cachefile
, NULL
);
2816 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2817 adios(NULL
, "unable to create temporary file in %s",
2820 ce
->ce_file
= add (tempfile
, NULL
);
2823 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2824 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2828 switch (child_id
= fork()) {
2830 adios ("fork", "unable to");
2834 argsplit_msgarg(&args
, urlprog
, &program
);
2835 app_msgarg(&args
, e
->eb_url
);
2836 app_msgarg(&args
, NULL
);
2837 dup2(fileno(ce
->ce_fp
), 1);
2838 close(fileno(ce
->ce_fp
));
2839 execvp(program
, args
.msgs
);
2840 fprintf(stderr
, "Unable to exec ");
2846 if (pidXwait(child_id
, NULL
)) {
2854 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2859 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2860 if ((fp
= fopen(cachefile
, "w"))) {
2862 FILE *gp
= ce
->ce_fp
;
2864 fseeko(gp
, 0, SEEK_SET
);
2866 while ((cc
= fread(buffer
, sizeof(*buffer
),
2867 sizeof(buffer
), gp
)) > 0)
2868 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2869 advise ("openURL", "fwrite");
2875 admonish(ce
->ce_file
, "error reading");
2876 (void) m_unlink (cachefile
);
2883 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2884 *file
= ce
->ce_file
;
2890 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2891 * has to be base64 decoded.
2894 readDigest (CT ct
, char *cp
)
2896 unsigned char *digest
;
2899 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2900 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2902 if (strlen ((char *) digest
) <= maxlen
) {
2903 memcpy (ct
->c_digest
, digest
, maxlen
);
2908 fprintf (stderr
, "MD5 digest=");
2909 for (i
= 0; i
< maxlen
; ++i
) {
2910 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2912 fprintf (stderr
, "\n");
2918 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2919 (int) strlen ((char *) digest
));
2929 /* Multipart parts might have content before the first subpart and/or
2930 after the last subpart that hasn't been stored anywhere else, so do
2933 get_leftover_mp_content (CT ct
, int before
/* or after */)
2935 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2937 int found_boundary
= 0;
2943 char *content
= NULL
;
2945 if (! m
) return NOTOK
;
2948 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2950 /* Isolate the beginning of this part to the beginning of the
2951 first subpart and save any content between them. */
2952 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2953 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2954 boundary
= concat ("--", m
->mp_start
, NULL
);
2956 struct part
*last_subpart
= NULL
;
2957 struct part
*subpart
;
2959 /* Go to the last subpart to get its end position. */
2960 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2961 last_subpart
= subpart
;
2964 if (last_subpart
== NULL
) return NOTOK
;
2966 /* Isolate the end of the last subpart to the end of this part
2967 and save any content between them. */
2968 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2969 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2970 boundary
= concat ("--", m
->mp_stop
, NULL
);
2973 /* Back up by 1 to pick up the newline. */
2974 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2976 /* Don't look beyond beginning of first subpart (before) or
2977 next part (after). */
2978 if (read
> max
) bufp
[read
-max
] = '\0';
2981 if (! strcmp (bufp
, boundary
)) {
2985 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2991 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2993 char *old_content
= content
;
2994 content
= concat (content
, bufp
, NULL
);
2998 ? concat ("\n", bufp
, NULL
)
2999 : concat (bufp
, NULL
);
3004 if (found_boundary
|| read
> max
) break;
3006 if (read
> max
) break;
3010 /* Skip the newline if that's all there is. */
3014 /* Remove trailing newline, except at EOF. */
3015 if ((before
|| ! feof (ct
->c_fp
)) &&
3016 (cp
= content
+ strlen (content
)) > content
&&
3021 if (strlen (content
) > 1) {
3023 m
->mp_content_before
= content
;
3025 m
->mp_content_after
= content
;
3040 ct_type_str (int type
) {
3042 case CT_APPLICATION
:
3043 return "application";
3059 return "unknown_type";
3065 ct_subtype_str (int type
, int subtype
) {
3067 case CT_APPLICATION
:
3069 case APPLICATION_OCTETS
:
3071 case APPLICATION_POSTSCRIPT
:
3072 return "postscript";
3074 return "unknown_app_subtype";
3078 case MESSAGE_RFC822
:
3080 case MESSAGE_PARTIAL
:
3082 case MESSAGE_EXTERNAL
:
3085 return "unknown_msg_subtype";
3091 case MULTI_ALTERNATE
:
3092 return "alternative";
3095 case MULTI_PARALLEL
:
3100 return "unknown_multipart_subtype";
3111 return "unknown_text_subtype";
3114 return "unknown_type";
3120 ct_str_type (const char *type
) {
3121 struct str2init
*s2i
;
3123 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3124 if (! strcasecmp (type
, s2i
->si_key
)) {
3128 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3137 ct_str_subtype (int type
, const char *subtype
) {
3141 case CT_APPLICATION
:
3142 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3143 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3147 return kv
->kv_value
;
3149 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3150 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3154 return kv
->kv_value
;
3156 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3157 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3161 return kv
->kv_value
;
3163 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3164 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3168 return kv
->kv_value
;
3175 /* Find the content type and InitFunc for the CT. */
3176 const struct str2init
*
3177 get_ct_init (int type
) {
3178 const struct str2init
*sp
;
3180 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3181 if (type
== sp
->si_val
) {
3190 ce_str (int encoding
) {
3195 return "quoted-printable";
3211 /* Find the content type and InitFunc for the content encoding method. */
3212 const struct str2init
*
3213 get_ce_method (const char *method
) {
3214 struct str2init
*sp
;
3216 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3217 if (! strcasecmp (method
, sp
->si_key
)) {
3226 * Parse a series of MIME attributes (or parameters) given a header as
3229 * Arguments include:
3231 * filename - Name of input file (for error messages)
3232 * fieldname - Name of field being processed
3233 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3234 * Updated to point to end of attributes when finished.
3235 * param_head - Pointer to head of parameter list
3236 * param_tail - Pointer to tail of parameter list
3237 * commentp - Pointer to header comment pointer (may be NULL)
3239 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3240 * DONE to indicate a benign error (minor parsing error, but the program
3245 parse_header_attrs (const char *filename
, const char *fieldname
,
3246 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3249 char *cp
= *header_attrp
;
3255 struct sectlist
*next
;
3261 struct sectlist
*sechead
;
3262 struct parmlist
*next
;
3263 } *pp
, *pp2
, *phead
= NULL
;
3265 while (*cp
== ';') {
3266 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3267 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3270 while (isspace ((unsigned char) *cp
))
3274 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3279 if (! suppress_extraneous_trailing_semicolon_warning
) {
3281 "extraneous trailing ';' in message %s's %s: "
3283 filename
, fieldname
);
3285 extraneous_trailing_semicolon
= 1;
3289 /* down case the attribute name */
3290 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3291 *dp
= tolower ((unsigned char) *dp
);
3293 for (up
= dp
; isspace ((unsigned char) *dp
);)
3295 if (dp
== cp
|| *dp
!= '=') {
3297 "invalid parameter in message %s's %s: "
3298 "field\n%*sparameter %s (error detected at offset %d)",
3299 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3304 * To handle RFC 2231, we have to deal with the following extensions:
3306 * name*=encoded-value
3307 * name*<N>=part-N-of-a-parameter-value
3308 * name*<N>*=encoded-part-N-of-a-parameter-value
3311 * If there's a * right before the equal sign, it's encoded.
3312 * If there's a * and one or more digits, then it's section N.
3314 * Remember we can have one or the other, or both. cp points to
3315 * beginning of name, up points past the last character in the
3319 for (vp
= cp
; vp
< up
; vp
++) {
3320 if (*vp
== '*' && vp
< up
- 1) {
3323 } else if (*vp
== '*' && vp
== up
- 1) {
3325 } else if (partial
) {
3326 if (isdigit((unsigned char) *vp
))
3327 index
= *vp
- '0' + index
* 10;
3329 advise (NULL
, "invalid parameter index in message %s's "
3330 "%s: field\n%*s(parameter %s)", filename
,
3331 fieldname
, strlen(invo_name
) + 2, "", cp
);
3340 * Break out the parameter name and value sections and allocate
3344 nameptr
= mh_xmalloc(len
+ 1);
3345 strncpy(nameptr
, cp
, len
);
3346 nameptr
[len
] = '\0';
3348 for (dp
++; isspace ((unsigned char) *dp
);)
3353 * Single quotes delimit the character set and language tag.
3354 * They are required on the first section (or a complete
3359 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3365 charset
= mh_xmalloc(len
+ 1);
3366 strncpy(charset
, dp
, len
);
3367 charset
[len
] = '\0';
3373 advise(NULL
, "missing charset in message %s's %s: "
3374 "field\n%*s(parameter %s)", filename
, fieldname
,
3375 strlen(invo_name
) + 2, "", nameptr
);
3381 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3388 lang
= mh_xmalloc(len
+ 1);
3389 strncpy(lang
, dp
, len
);
3396 advise(NULL
, "missing language tag in message %s's %s: "
3397 "field\n%*s(parameter %s)", filename
, fieldname
,
3398 strlen(invo_name
) + 2, "", nameptr
);
3408 * At this point vp should be pointing at the beginning
3409 * of the encoded value/section. Continue until we reach
3410 * the end or get whitespace. But first, calculate the
3411 * length so we can allocate the correct buffer size.
3414 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3416 if (*(vp
+ 1) == '\0' ||
3417 !isxdigit((unsigned char) *(vp
+ 1)) ||
3418 *(vp
+ 2) == '\0' ||
3419 !isxdigit((unsigned char) *(vp
+ 2))) {
3420 advise(NULL
, "invalid encoded sequence in message "
3421 "%s's %s: field\n%*s(parameter %s)",
3422 filename
, fieldname
, strlen(invo_name
) + 2,
3434 up
= valptr
= mh_xmalloc(len
+ 1);
3436 for (vp
= dp
; istoken(*vp
); vp
++) {
3438 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3449 * A "normal" string. If it's got a leading quote, then we
3450 * strip the quotes out. Otherwise go until we reach the end
3451 * or get whitespace. Note we scan it twice; once to get the
3452 * length, then the second time copies it into the destination
3459 for (cp
= dp
+ 1;;) {
3464 "invalid quoted-string in message %s's %s: "
3465 "field\n%*s(parameter %s)",
3466 filename
, fieldname
, strlen(invo_name
) + 2, "",
3487 for (cp
= dp
; istoken (*cp
); cp
++) {
3492 valptr
= mh_xmalloc(len
+ 1);
3496 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3504 strncpy(valptr
, cp
= dp
, len
);
3512 * If 'partial' is set, we don't allocate a parameter now. We
3513 * put it on the parameter linked list to be reassembled later.
3515 * "phead" points to a list of all parameters we need to reassemble.
3516 * Each parameter has a list of sections. We insert the sections in
3521 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3522 if (strcasecmp(nameptr
, pp
->name
) == 0)
3534 * Insert this into the section linked list
3542 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3543 sp
->next
= pp
->sechead
;
3546 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3547 if (sp2
->index
== sp
->index
) {
3548 advise (NULL
, "duplicate index (%d) in message "
3549 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3550 filename
, fieldname
, strlen(invo_name
) + 2, "",
3555 if (sp2
->index
< sp
->index
&&
3556 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3557 sp
->next
= sp2
->next
;
3564 advise(NULL
, "Internal error: cannot insert partial "
3565 "param in message %s's %s: field\n%*s(parameter %s)",
3566 filename
, fieldname
, strlen(invo_name
) + 2, "",
3574 * Save our charset and lang tags.
3577 if (index
== 0 && encoded
) {
3578 mh_xfree(pp
->charset
);
3579 pp
->charset
= charset
;
3584 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3585 pm
->pm_charset
= charset
;
3589 while (isspace ((unsigned char) *cp
))
3593 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3599 * Now that we're done, reassemble all of the partial parameters.
3602 for (pp
= phead
; pp
!= NULL
; ) {
3606 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3607 if (sp
->index
!= pindex
++) {
3608 advise(NULL
, "missing section %d for parameter in "
3609 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3610 filename
, fieldname
, strlen(invo_name
) + 2, "",
3617 p
= q
= mh_xmalloc(tlen
+ 1);
3618 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3619 memcpy(q
, sp
->value
, sp
->len
);
3629 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3630 pm
->pm_charset
= pp
->charset
;
3631 pm
->pm_lang
= pp
->lang
;
3642 * Return the charset for a particular content type.
3646 content_charset (CT ct
) {
3647 char *ret_charset
= NULL
;
3649 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3651 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3656 * Create a string based on a list of output parameters. Assume that this
3657 * parameter string will be appended to an existing header, so start out
3658 * with the separator (;). Perform RFC 2231 encoding when necessary.
3662 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3664 char *paramout
= NULL
;
3665 char line
[CPERLIN
* 2], *q
;
3666 int curlen
, index
, cont
, encode
, i
;
3667 size_t valoff
, numchars
;
3669 while (params
!= NULL
) {
3675 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3678 if (strlen(params
->pm_name
) > CPERLIN
) {
3679 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3684 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3687 * Loop until we get a parameter that fits within a line. We
3688 * assume new lines start with a tab, so check our overflow based
3698 * At this point we're definitely continuing the line, so
3699 * be sure to include the parameter name and section index.
3702 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3703 params
->pm_name
, index
);
3706 * Both of these functions do a NUL termination
3710 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3711 numchars
, valoff
, index
);
3713 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3723 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3728 * "line" starts with a ;\n\t, so that doesn't count against
3729 * the length. But add 8 since it starts with a tab; that's
3730 * how we end up with 5.
3733 initialwidth
= strlen(line
) + 5;
3736 * At this point the line should be built, so add it to our
3737 * current output buffer.
3740 paramout
= add(line
, paramout
);
3744 * If this won't fit on the line, start a new one. Save room in
3745 * case we need a semicolon on the end
3748 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3760 * At this point, we're either finishing a contined parameter, or
3761 * we're working on a new one.
3765 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3766 params
->pm_name
, index
);
3768 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3773 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3774 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3776 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3777 strlen(params
->pm_value
+ valoff
), valoff
);
3784 paramout
= add(line
, paramout
);
3785 initialwidth
+= strlen(line
);
3787 params
= params
->pm_next
;
3791 *offsetout
= initialwidth
;
3797 * Calculate the size of a parameter.
3801 * pm - The parameter being output
3802 * index - If continuing the parameter, the index of the section
3804 * valueoff - The current offset into the parameter value that we're
3805 * working on (previous sections have consumed valueoff bytes).
3806 * encode - Set if we should perform encoding on this parameter section
3807 * (given that we're consuming bytesfit bytes).
3808 * cont - Set if the remaining data in value will not fit on a single
3809 * line and will need to be continued.
3810 * bytesfit - The number of bytes that we can consume from the parameter
3811 * value and still fit on a completely new line. The
3812 * calculation assumes the new line starts with a tab,
3813 * includes the parameter name and any encoding, and fits
3814 * within CPERLIN bytes. Will always be at least 1.
3818 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3821 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3822 size_t len
= 0, fit
= 0;
3823 int fitlimit
= 0, eightbit
, maxfit
;
3828 * Add up the length. First, start with the parameter name.
3831 len
= strlen(pm
->pm_name
);
3834 * Scan the parameter value and see if we need to do encoding for this
3838 eightbit
= contains8bit(start
, NULL
);
3841 * Determine if we need to encode this section. Encoding is necessary if:
3843 * - There are any 8-bit characters at all and we're on the first
3845 * - There are 8-bit characters within N bytes of our section start.
3846 * N is calculated based on the number of bytes it would take to
3847 * reach CPERLIN. Specifically:
3848 * 8 (starting tab) +
3849 * strlen(param name) +
3850 * 4 ('* for section marker, '=', opening/closing '"')
3852 * is the number of bytes used by everything that isn't part of the
3853 * value. So that gets subtracted from CPERLIN.
3856 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3857 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3858 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3862 len
++; /* Add in equal sign */
3866 * We're using maxfit as a marker for how many characters we can
3867 * fit into the line. Bump it by two because we're not using quotes
3874 * If we don't have a charset or language tag in this parameter,
3878 if (! pm
->pm_charset
) {
3879 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3880 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3881 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3882 "local character set is US-ASCII", pm
->pm_name
);
3885 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3887 len
++; /* For the encoding marker */
3890 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3895 * We know we definitely need to include an index. maxfit already
3896 * includes the section marker.
3898 len
+= strlen(indexchar
);
3900 for (p
= start
; *p
!= '\0'; p
++) {
3901 if (isparamencode(*p
)) {
3909 * Just so there's no confusion: maxfit is counting OUTPUT
3910 * characters (post-encoding). fit is counting INPUT characters.
3912 if (! fitlimit
&& maxfit
>= 0)
3914 else if (! fitlimit
)
3919 * Calculate the string length, but add room for quoting \
3920 * and " if necessary. Also account for quotes at beginning
3923 for (p
= start
; *p
!= '\0'; p
++) {
3934 if (! fitlimit
&& maxfit
>= 0)
3936 else if (! fitlimit
)
3953 * Output an encoded parameter string.
3957 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3958 size_t valueoff
, int index
)
3960 size_t outlen
= 0, n
;
3961 char *endptr
= output
+ len
, *p
;
3964 * First, output the marker for an encoded string.
3972 * If the index is 0, output the character set and language tag.
3973 * If theses were NULL, they should have already been filled in
3978 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3982 if (output
> endptr
) {
3983 advise(NULL
, "Internal error: parameter buffer overflow");
3989 * Copy over the value, encoding if necessary
3992 p
= pm
->pm_value
+ valueoff
;
3993 while (valuelen
-- > 0) {
3994 if (isparamencode(*p
)) {
3995 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4002 if (output
> endptr
) {
4003 advise(NULL
, "Internal error: parameter buffer overflow");
4014 * Output a "normal" parameter, without encoding. Be sure to escape
4015 * quotes and backslashes if necessary.
4019 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4023 char *endptr
= output
+ len
, *p
;
4029 p
= pm
->pm_value
+ valueoff
;
4031 while (valuelen
-- > 0) {
4041 if (output
> endptr
) {
4042 advise(NULL
, "Internal error: parameter buffer overflow");
4047 if (output
- 2 > endptr
) {
4048 advise(NULL
, "Internal error: parameter buffer overflow");
4059 * Add a parameter to the parameter linked list
4063 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4068 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4069 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4072 (*last
)->pm_next
= pm
;
4083 * Either replace a current parameter with a new value, or add the parameter
4084 * to the parameter linked list.
4088 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4092 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4093 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4095 * If nocopy is set, it's assumed that we own both name
4096 * and value. We don't need name, so we discard it now.
4101 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4106 return add_param(first
, last
, name
, value
, nocopy
);
4110 * Retrieve a parameter value from a parameter linked list. If the parameter
4111 * value needs converted to the local character set, do that now.
4115 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4117 while (first
!= NULL
) {
4118 if (strcasecmp(name
, first
->pm_name
) == 0) {
4120 return first
->pm_value
;
4121 return getcpy(get_param_value(first
, replace
));
4123 first
= first
->pm_next
;
4130 * Return a parameter value, converting to the local character set if
4134 char *get_param_value(PM pm
, char replace
)
4136 static char buffer
[4096]; /* I hope no parameters are larger */
4137 size_t bufsize
= sizeof(buffer
);
4142 ICONV_CONST
char *p
;
4143 #else /* HAVE_ICONV */
4145 #endif /* HAVE_ICONV */
4150 * If we don't have a character set indicated, it's assumed to be
4151 * US-ASCII. If it matches our character set, we don't need to convert
4155 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4156 strlen(pm
->pm_charset
))) {
4157 return pm
->pm_value
;
4161 * In this case, we need to convert. If we have iconv support, use
4162 * that. Otherwise, go through and simply replace every non-ASCII
4163 * character with the substitution character.
4168 bufsize
= sizeof(buffer
);
4169 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4171 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4172 if (cd
== (iconv_t
) -1) {
4176 inbytes
= strlen(pm
->pm_value
);
4180 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4181 if (errno
!= EILSEQ
) {
4186 * Reset shift state, substitute our character,
4187 * try to restart conversion.
4190 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4203 for (++p
, --inbytes
;
4204 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4223 #endif /* HAVE_ICONV */
4226 * Take everything non-ASCII and substituite the replacement character
4230 bufsize
= sizeof(buffer
);
4231 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4232 /* FIXME: !iscntrl should perhaps be isprint as that allows all
4233 * classes bar cntrl, whereas the cntrl class can include those
4234 * in space and blank.
4235 * http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html */
4236 if (isascii((unsigned char) *p
) && !iscntrl((unsigned char) *p
))