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
);
661 /* down case the content type string */
662 for (dp
= ci
->ci_type
; *dp
; dp
++)
663 *dp
= tolower ((unsigned char) *dp
);
665 while (isspace ((unsigned char) *cp
))
668 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
669 &ci
->ci_comment
) == NOTOK
)
674 ci
->ci_subtype
= add ("", NULL
);
679 while (isspace ((unsigned char) *cp
))
682 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
683 &ci
->ci_comment
) == NOTOK
)
686 for (dp
= cp
; istoken (*dp
); dp
++)
689 ci
->ci_subtype
= add (cp
, NULL
); /* store the content subtype */
692 if (!*ci
->ci_subtype
) {
694 "invalid %s: field in message %s (empty subtype for \"%s\")",
695 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
699 /* down case the content subtype string */
700 for (dp
= ci
->ci_subtype
; *dp
; dp
++)
701 *dp
= tolower ((unsigned char) *dp
);
704 while (isspace ((unsigned char) *cp
))
707 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
708 &ci
->ci_comment
) == NOTOK
)
711 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
712 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
713 &ci
->ci_comment
)) != OK
) {
714 return status
== NOTOK
? NOTOK
: OK
;
718 * Get any <Content-Id> given in buffer
720 if (magic
&& *cp
== '<') {
725 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
726 advise (NULL
, "invalid ID in message %s", ct
->c_file
);
732 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
738 while (isspace ((unsigned char) *cp
))
743 * Get any [Content-Description] given in buffer.
745 if (magic
&& *cp
== '[') {
747 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
751 advise (NULL
, "invalid description in message %s", ct
->c_file
);
759 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
765 while (isspace ((unsigned char) *cp
))
770 * Get any {Content-Disposition} given in buffer.
772 if (magic
&& *cp
== '{') {
774 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
778 advise (NULL
, "invalid disposition in message %s", ct
->c_file
);
786 if (get_dispo(cp
, ct
, 1) != OK
)
792 while (isspace ((unsigned char) *cp
))
797 * Get any extension directives (right now just the content transfer
798 * encoding, but maybe others) that we care about.
801 if (magic
&& *cp
== '*') {
803 * See if it's a CTE we match on
808 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
812 advise (NULL
, "invalid null transfer encoding specification");
819 ct
->c_reqencoding
= CE_UNKNOWN
;
821 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
822 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
823 ct
->c_reqencoding
= kv
->kv_value
;
828 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
829 advise (NULL
, "invalid CTE specification: \"%s\"", dp
);
833 while (isspace ((unsigned char) *cp
))
838 * Check if anything is left over
842 ci
->ci_magic
= add (cp
, NULL
);
844 /* If there is a Content-Disposition header and it doesn't
845 have a *filename=, extract it from the magic contents.
846 The r1bindex call skips any leading directory
848 if (ct
->c_dispo_type
&&
849 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
850 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
851 r1bindex(ci
->ci_magic
, '/'), 0);
856 "extraneous information in message %s's %s: field\n%*s(%s)",
857 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
865 * Parse out a Content-Disposition header. A lot of this is cribbed from
869 get_dispo (char *cp
, CT ct
, int buildflag
)
871 char *dp
, *dispoheader
;
876 * Save the whole copy of the Content-Disposition header, unless we're
877 * processing a mhbuild directive. A NULL c_dispo will be a flag to
878 * mhbuild that the disposition header needs to be generated at that
882 dispoheader
= cp
= add(cp
, NULL
);
884 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
887 /* change newlines to spaces */
888 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
891 /* trim trailing spaces */
892 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
893 if (!isspace ((unsigned char) *dp
))
898 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
900 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
906 for (dp
= cp
; istoken (*dp
); dp
++)
909 ct
->c_dispo_type
= add (cp
, NULL
); /* store disposition type */
912 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
915 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
916 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
918 if (status
== NOTOK
) {
924 "extraneous information in message %s's %s: field\n%*s(%s)",
925 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
931 ct
->c_dispo
= dispoheader
;
938 get_comment (const char *filename
, const char *fieldname
, char **ap
,
943 char c
, buffer
[BUFSIZ
], *dp
;
953 advise (NULL
, "invalid comment in message %s's %s: field",
954 filename
, fieldname
);
959 if ((c
= *cp
++) == '\0')
982 if ((dp
= *commentp
)) {
983 *commentp
= concat (dp
, " ", buffer
, NULL
);
986 *commentp
= add (buffer
, NULL
);
990 while (isspace ((unsigned char) *cp
))
1001 * Handles content types audio, image, and video.
1002 * There's not much to do right here.
1010 return OK
; /* not much to do here */
1021 char buffer
[BUFSIZ
];
1026 CI ci
= &ct
->c_ctinfo
;
1028 /* check for missing subtype */
1029 if (!*ci
->ci_subtype
)
1030 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1033 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1035 /* allocate text character set structure */
1037 ct
->c_ctparams
= (void *) t
;
1039 /* scan for charset parameter */
1040 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1041 if (!strcasecmp (pm
->pm_name
, "charset"))
1044 /* check if content specified a character set */
1046 chset
= pm
->pm_value
;
1047 t
->tx_charset
= CHARSET_SPECIFIED
;
1049 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1053 * If we can not handle character set natively,
1054 * then check profile for string to modify the
1055 * terminal or display method.
1057 * termproc is for mhshow, though mhlist -debug prints it, too.
1059 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1060 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1061 if ((cp
= context_find (buffer
)))
1062 ct
->c_termproc
= mh_xstrdup(cp
);
1074 InitMultiPart (CT ct
)
1084 struct multipart
*m
;
1085 struct part
*part
, **next
;
1086 CI ci
= &ct
->c_ctinfo
;
1091 * The encoding for multipart messages must be either
1092 * 7bit, 8bit, or binary (per RFC2045).
1094 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1095 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1096 /* Copy the Content-Transfer-Encoding header field body so we can
1097 remove any trailing whitespace and leading blanks from it. */
1098 char *cte
= add (ct
->c_celine
? ct
->c_celine
: "(null)", NULL
);
1100 bp
= cte
+ strlen (cte
) - 1;
1101 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1102 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1105 "\"%s/%s\" type in message %s must be encoded in\n"
1106 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1107 "mhfixmsg -fixcte can fix it, or\n"
1108 "manually edit the file and change the \"%s\"\n"
1109 "Content-Transfer-Encoding to one of those. For now",
1110 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1117 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1120 * Check for "boundary" parameter, which is
1121 * required for multipart messages.
1124 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1125 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1131 /* complain if boundary parameter is missing */
1134 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1135 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1139 /* allocate primary structure for multipart info */
1141 ct
->c_ctparams
= (void *) m
;
1143 /* check if boundary parameter contains only whitespace characters */
1144 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1147 advise (NULL
, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1148 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1152 /* remove trailing whitespace from boundary parameter */
1153 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1154 if (!isspace ((unsigned char) *dp
))
1158 /* record boundary separators */
1159 m
->mp_start
= concat (bp
, "\n", NULL
);
1160 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1162 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1163 advise (ct
->c_file
, "unable to open for reading");
1167 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1169 next
= &m
->mp_parts
;
1173 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1178 if (bufp
[0] != '-' || bufp
[1] != '-')
1181 if (strcmp (bufp
+ 2, m
->mp_start
))
1186 next
= &part
->mp_next
;
1188 if (!(p
= get_content (fp
, ct
->c_file
,
1189 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1197 fseek (fp
, pos
, SEEK_SET
);
1200 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1204 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1205 if (p
->c_end
< p
->c_begin
)
1206 p
->c_begin
= p
->c_end
;
1211 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1216 if (! suppress_bogus_mp_content_warning
) {
1217 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1219 bogus_mp_content
= 1;
1221 if (!inout
&& part
) {
1223 p
->c_end
= ct
->c_end
;
1225 if (p
->c_begin
>= p
->c_end
) {
1226 for (next
= &m
->mp_parts
; *next
!= part
;
1227 next
= &((*next
)->mp_next
))
1231 free ((char *) part
);
1236 /* reverse the order of the parts for multipart/alternative */
1237 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1243 * label all subparts with part number, and
1244 * then initialize the content of the subpart.
1249 char partnam
[BUFSIZ
];
1252 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1253 pp
= partnam
+ strlen (partnam
);
1258 for (part
= m
->mp_parts
, partnum
= 1; part
;
1259 part
= part
->mp_next
, partnum
++) {
1262 sprintf (pp
, "%d", partnum
);
1263 p
->c_partno
= add (partnam
, NULL
);
1265 /* initialize the content of the subparts */
1266 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1275 get_leftover_mp_content (ct
, 1);
1276 get_leftover_mp_content (ct
, 0);
1286 * reverse the order of the parts of a multipart/alternative,
1287 * presumably to put the "most favored" alternative first, for
1288 * ease of choosing/displaying it later on. from a mail message on
1289 * nmh-workers, from kenh:
1290 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1291 * see code in mhn that did the same thing... Acccording to the RCS
1292 * logs, that code was around from the initial checkin of mhn.c by
1293 * John Romine in 1992, which is as far back as we have."
1296 reverse_parts (CT ct
)
1298 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1302 /* Reverse the order of its parts by walking the mp_parts list
1303 and pushing each node to the front. */
1304 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1305 next
= part
->mp_next
;
1306 part
->mp_next
= m
->mp_parts
;
1312 move_preferred_part (CT ct
, char *type
, char *subtype
)
1314 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1315 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1319 /* move the matching part(s) to the head of the list: walk the
1320 * list of parts, move matching parts to a new list (maintaining
1321 * their order), and finally, concatenate the old list onto the
1328 head
->mp_next
= m
->mp_parts
;
1329 nhead
->mp_next
= NULL
;
1333 part
= head
->mp_next
;
1334 while (part
!= NULL
) {
1335 ci
= &part
->mp_part
->c_ctinfo
;
1336 if (!strcasecmp(ci
->ci_type
, type
) &&
1337 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1338 prev
->mp_next
= part
->mp_next
;
1339 part
->mp_next
= NULL
;
1340 ntail
->mp_next
= part
;
1342 part
= prev
->mp_next
;
1345 part
= prev
->mp_next
;
1348 ntail
->mp_next
= head
->mp_next
;
1349 m
->mp_parts
= nhead
->mp_next
;
1354 * move parts that match the user's preferences (-prefer) to the head
1355 * of the line. process preferences in reverse so first one given
1356 * ends up first in line
1362 for (i
= npreferred
-1; i
>= 0; i
--)
1363 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1368 /* parse_mime() arranges alternates in reverse (priority) order. This
1369 function can be used to reverse them back. This will put, for
1370 example, a text/plain part before a text/html part in a
1371 multipart/alternative part, for example, where it belongs. */
1373 reverse_alternative_parts (CT ct
) {
1374 if (ct
->c_type
== CT_MULTIPART
) {
1375 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1378 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1382 /* And call recursively on each part of a multipart. */
1383 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1384 reverse_alternative_parts (part
->mp_part
);
1397 CI ci
= &ct
->c_ctinfo
;
1399 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1401 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1402 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1406 /* check for missing subtype */
1407 if (!*ci
->ci_subtype
)
1408 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1411 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1413 switch (ct
->c_subtype
) {
1414 case MESSAGE_RFC822
:
1417 case MESSAGE_PARTIAL
:
1423 ct
->c_ctparams
= (void *) p
;
1425 /* scan for parameters "id", "number", and "total" */
1426 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1427 if (!strcasecmp (pm
->pm_name
, "id")) {
1428 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1431 if (!strcasecmp (pm
->pm_name
, "number")) {
1432 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1433 || p
->pm_partno
< 1) {
1436 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1437 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1438 ct
->c_file
, TYPE_FIELD
);
1443 if (!strcasecmp (pm
->pm_name
, "total")) {
1444 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1453 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1455 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1456 ci
->ci_type
, ci
->ci_subtype
,
1457 ct
->c_file
, TYPE_FIELD
);
1463 case MESSAGE_EXTERNAL
:
1471 ct
->c_ctparams
= (void *) e
;
1474 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1475 advise (ct
->c_file
, "unable to open for reading");
1479 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1481 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1489 p
->c_ceopenfnx
= NULL
;
1490 if ((exresult
= params_external (ct
, 0)) != NOTOK
1491 && p
->c_ceopenfnx
== openMail
) {
1495 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1497 content_error (NULL
, ct
,
1498 "empty body for access-type=mail-server");
1502 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1503 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1505 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1507 adios ("failed", "fread");
1510 adios (NULL
, "unexpected EOF from fread");
1513 bp
+= cc
, size
-= cc
;
1520 p
->c_end
= p
->c_begin
;
1525 if (exresult
== NOTOK
)
1527 if (e
->eb_flags
== NOTOK
)
1530 switch (p
->c_type
) {
1535 if (p
->c_subtype
!= MESSAGE_RFC822
)
1539 e
->eb_partno
= ct
->c_partno
;
1541 (*p
->c_ctinitfnx
) (p
);
1556 params_external (CT ct
, int composing
)
1559 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1560 CI ci
= &ct
->c_ctinfo
;
1562 ct
->c_ceopenfnx
= NULL
;
1563 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1564 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1565 struct str2init
*s2i
;
1566 CT p
= e
->eb_content
;
1568 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1569 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1572 e
->eb_access
= pm
->pm_value
;
1573 e
->eb_flags
= NOTOK
;
1574 p
->c_encoding
= CE_EXTERNAL
;
1577 e
->eb_access
= s2i
->si_key
;
1578 e
->eb_flags
= s2i
->si_val
;
1579 p
->c_encoding
= CE_EXTERNAL
;
1581 /* Call the Init function for this external type */
1582 if ((*s2i
->si_init
)(p
) == NOTOK
)
1586 if (!strcasecmp (pm
->pm_name
, "name")) {
1587 e
->eb_name
= pm
->pm_value
;
1590 if (!strcasecmp (pm
->pm_name
, "permission")) {
1591 e
->eb_permission
= pm
->pm_value
;
1594 if (!strcasecmp (pm
->pm_name
, "site")) {
1595 e
->eb_site
= pm
->pm_value
;
1598 if (!strcasecmp (pm
->pm_name
, "directory")) {
1599 e
->eb_dir
= pm
->pm_value
;
1602 if (!strcasecmp (pm
->pm_name
, "mode")) {
1603 e
->eb_mode
= pm
->pm_value
;
1606 if (!strcasecmp (pm
->pm_name
, "size")) {
1607 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1610 if (!strcasecmp (pm
->pm_name
, "server")) {
1611 e
->eb_server
= pm
->pm_value
;
1614 if (!strcasecmp (pm
->pm_name
, "subject")) {
1615 e
->eb_subject
= pm
->pm_value
;
1618 if (!strcasecmp (pm
->pm_name
, "url")) {
1620 * According to RFC 2017, we have to remove all whitespace from
1624 char *u
, *p
= pm
->pm_value
;
1625 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1627 for (; *p
!= '\0'; p
++) {
1628 if (! isspace((unsigned char) *p
))
1635 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1636 e
->eb_body
= getcpy (pm
->pm_value
);
1641 if (!e
->eb_access
) {
1643 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1644 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1657 InitApplication (CT ct
)
1659 CI ci
= &ct
->c_ctinfo
;
1662 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1669 * TRANSFER ENCODINGS
1673 init_encoding (CT ct
, OpenCEFunc openfnx
)
1675 ct
->c_ceopenfnx
= openfnx
;
1676 ct
->c_ceclosefnx
= close_encoding
;
1677 ct
->c_cesizefnx
= size_encoding
;
1684 close_encoding (CT ct
)
1686 CE ce
= &ct
->c_cefile
;
1695 static unsigned long
1696 size_encoding (CT ct
)
1701 CE ce
= &ct
->c_cefile
;
1704 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1705 return (long) st
.st_size
;
1708 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1709 return (long) st
.st_size
;
1713 if (ct
->c_encoding
== CE_EXTERNAL
)
1714 return (ct
->c_end
- ct
->c_begin
);
1717 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1718 return (ct
->c_end
- ct
->c_begin
);
1720 if (fstat (fd
, &st
) != NOTOK
)
1721 size
= (long) st
.st_size
;
1725 (*ct
->c_ceclosefnx
) (ct
);
1737 return init_encoding (ct
, openBase64
);
1742 openBase64 (CT ct
, char **file
)
1745 int fd
, own_ct_fp
= 0;
1746 char *cp
, *buffer
= NULL
;
1747 /* sbeck -- handle suffixes */
1749 CE ce
= &ct
->c_cefile
;
1750 unsigned char *decoded
;
1752 unsigned char digest
[16];
1755 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1760 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1761 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1767 if (*file
== NULL
) {
1770 ce
->ce_file
= add (*file
, NULL
);
1774 /* sbeck@cise.ufl.edu -- handle suffixes */
1776 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1777 if (ce
->ce_unlink
) {
1778 /* Create temporary file with filename extension. */
1779 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1780 adios(NULL
, "unable to create temporary file in %s",
1784 ce
->ce_file
= add (cp
, ce
->ce_file
);
1786 } else if (*file
== NULL
) {
1788 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1789 adios(NULL
, "unable to create temporary file in %s",
1792 ce
->ce_file
= add (tempfile
, NULL
);
1795 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1796 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1800 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1801 adios (NULL
, "internal error(1)");
1803 buffer
= mh_xmalloc (len
+ 1);
1806 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1807 content_error (ct
->c_file
, ct
, "unable to open for reading");
1813 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1816 switch (cc
= read (fd
, cp
, len
)) {
1818 content_error (ct
->c_file
, ct
, "error reading from");
1822 content_error (NULL
, ct
, "premature eof");
1833 /* decodeBase64() requires null-terminated input. */
1836 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1837 ct
->c_digested
? digest
: NULL
) == OK
) {
1839 unsigned char *decoded_p
= decoded
;
1840 for (i
= 0; i
< decoded_len
; ++i
) {
1841 putc (*decoded_p
++, ce
->ce_fp
);
1843 free ((char *) decoded
);
1844 if (ferror (ce
->ce_fp
)) {
1845 content_error (ce
->ce_file
, ct
, "error writing to");
1849 if (ct
->c_digested
) {
1850 if (memcmp(digest
, ct
->c_digest
,
1851 sizeof(digest
) / sizeof(digest
[0]))) {
1852 content_error (NULL
, ct
,
1853 "content integrity suspect (digest mismatch) -- continuing");
1856 fprintf (stderr
, "content integrity confirmed\n");
1864 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1866 if (fflush (ce
->ce_fp
)) {
1867 content_error (ce
->ce_file
, ct
, "error writing to");
1871 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1874 *file
= ce
->ce_file
;
1880 return fileno (ce
->ce_fp
);
1887 free_encoding (ct
, 0);
1897 static char hex2nib
[0x80] = {
1898 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1905 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1909 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1910 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1911 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1913 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1920 return init_encoding (ct
, openQuoted
);
1925 openQuoted (CT ct
, char **file
)
1927 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1933 CE ce
= &ct
->c_cefile
;
1934 /* sbeck -- handle suffixes */
1939 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1944 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1945 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1951 if (*file
== NULL
) {
1954 ce
->ce_file
= add (*file
, NULL
);
1958 /* sbeck@cise.ufl.edu -- handle suffixes */
1960 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1961 if (ce
->ce_unlink
) {
1962 /* Create temporary file with filename extension. */
1963 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1964 adios(NULL
, "unable to create temporary file in %s",
1968 ce
->ce_file
= add (cp
, ce
->ce_file
);
1970 } else if (*file
== NULL
) {
1972 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1973 adios(NULL
, "unable to create temporary file in %s",
1976 ce
->ce_file
= add (tempfile
, NULL
);
1979 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1980 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1984 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1985 adios (NULL
, "internal error(2)");
1988 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1989 content_error (ct
->c_file
, ct
, "unable to open for reading");
1995 if ((digested
= ct
->c_digested
))
1996 MD5Init (&mdContext
);
2003 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2005 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2006 content_error (NULL
, ct
, "premature eof");
2010 if ((cc
= gotlen
) > len
)
2014 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2015 if (!isspace ((unsigned char) *ep
))
2019 for (; cp
< ep
; cp
++) {
2021 /* in an escape sequence */
2023 /* at byte 1 of an escape sequence */
2024 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2025 /* next is byte 2 */
2028 /* at byte 2 of an escape sequence */
2030 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2031 putc (mask
, ce
->ce_fp
);
2033 MD5Update (&mdContext
, &mask
, 1);
2034 if (ferror (ce
->ce_fp
)) {
2035 content_error (ce
->ce_file
, ct
, "error writing to");
2038 /* finished escape sequence; next may be literal or a new
2039 * escape sequence */
2042 /* on to next byte */
2046 /* not in an escape sequence */
2048 /* starting an escape sequence, or invalid '='? */
2049 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2050 /* "=\n" soft line break, eat the \n */
2054 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2055 /* We don't have 2 bytes left, so this is an invalid
2056 * escape sequence; just show the raw bytes (below). */
2057 } else if (isxdigit ((unsigned char) cp
[1]) &&
2058 isxdigit ((unsigned char) cp
[2])) {
2059 /* Next 2 bytes are hex digits, making this a valid escape
2060 * sequence; let's decode it (above). */
2064 /* One or both of the next 2 is out of range, making this
2065 * an invalid escape sequence; just show the raw bytes
2070 /* Just show the raw byte. */
2071 putc (*cp
, ce
->ce_fp
);
2074 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2076 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2079 if (ferror (ce
->ce_fp
)) {
2080 content_error (ce
->ce_file
, ct
, "error writing to");
2086 content_error (NULL
, ct
,
2087 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2091 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2093 if (fflush (ce
->ce_fp
)) {
2094 content_error (ce
->ce_file
, ct
, "error writing to");
2099 unsigned char digest
[16];
2101 MD5Final (digest
, &mdContext
);
2102 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2103 sizeof(digest
) / sizeof(digest
[0])))
2104 content_error (NULL
, ct
,
2105 "content integrity suspect (digest mismatch) -- continuing");
2108 fprintf (stderr
, "content integrity confirmed\n");
2111 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2114 *file
= ce
->ce_file
;
2120 return fileno (ce
->ce_fp
);
2123 free_encoding (ct
, 0);
2140 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2143 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2149 open7Bit (CT ct
, char **file
)
2151 int cc
, fd
, len
, own_ct_fp
= 0;
2152 char buffer
[BUFSIZ
];
2153 /* sbeck -- handle suffixes */
2156 CE ce
= &ct
->c_cefile
;
2159 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2164 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2165 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2171 if (*file
== NULL
) {
2174 ce
->ce_file
= add (*file
, NULL
);
2178 /* sbeck@cise.ufl.edu -- handle suffixes */
2180 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2181 if (ce
->ce_unlink
) {
2182 /* Create temporary file with filename extension. */
2183 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2184 adios(NULL
, "unable to create temporary file in %s",
2188 ce
->ce_file
= add (cp
, ce
->ce_file
);
2190 } else if (*file
== NULL
) {
2192 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2193 adios(NULL
, "unable to create temporary file in %s",
2196 ce
->ce_file
= add (tempfile
, NULL
);
2199 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2200 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2204 if (ct
->c_type
== CT_MULTIPART
) {
2205 CI ci
= &ct
->c_ctinfo
;
2209 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2210 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2211 + 1 + strlen (ci
->ci_subtype
);
2212 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2215 fputs (buffer
, ce
->ce_fp
);
2219 if (ci
->ci_comment
) {
2220 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2221 fputs ("\n\t", ce
->ce_fp
);
2225 putc (' ', ce
->ce_fp
);
2228 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2231 fprintf (ce
->ce_fp
, "\n");
2233 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2235 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2237 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2238 fprintf (ce
->ce_fp
, "\n");
2241 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2242 adios (NULL
, "internal error(3)");
2245 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2246 content_error (ct
->c_file
, ct
, "unable to open for reading");
2252 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2254 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2256 content_error (ct
->c_file
, ct
, "error reading from");
2260 content_error (NULL
, ct
, "premature eof");
2268 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2269 advise ("open7Bit", "fwrite");
2271 if (ferror (ce
->ce_fp
)) {
2272 content_error (ce
->ce_file
, ct
, "error writing to");
2277 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2279 if (fflush (ce
->ce_fp
)) {
2280 content_error (ce
->ce_file
, ct
, "error writing to");
2284 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2287 *file
= ce
->ce_file
;
2292 return fileno (ce
->ce_fp
);
2295 free_encoding (ct
, 0);
2309 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2311 char cachefile
[BUFSIZ
];
2314 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2319 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2320 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2326 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2327 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2328 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2329 ce
->ce_file
= mh_xstrdup(cachefile
);
2333 admonish (cachefile
, "unable to fopen for reading");
2336 *fd
= fileno (ce
->ce_fp
);
2340 *file
= ce
->ce_file
;
2341 *fd
= fileno (ce
->ce_fp
);
2352 return init_encoding (ct
, openFile
);
2357 openFile (CT ct
, char **file
)
2360 char cachefile
[BUFSIZ
];
2361 struct exbody
*e
= ct
->c_ctexbody
;
2362 CE ce
= &ct
->c_cefile
;
2364 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2376 content_error (NULL
, ct
, "missing name parameter");
2380 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2383 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2384 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2388 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2389 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2390 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2394 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2395 if ((fp
= fopen (cachefile
, "w"))) {
2397 char buffer
[BUFSIZ
];
2398 FILE *gp
= ce
->ce_fp
;
2400 fseek (gp
, 0L, SEEK_SET
);
2402 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2404 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2405 advise ("openFile", "fwrite");
2410 admonish (ce
->ce_file
, "error reading");
2411 (void) m_unlink (cachefile
);
2415 admonish (cachefile
, "error writing");
2416 (void) m_unlink (cachefile
);
2423 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2424 *file
= ce
->ce_file
;
2425 return fileno (ce
->ce_fp
);
2435 return init_encoding (ct
, openFTP
);
2440 openFTP (CT ct
, char **file
)
2442 int cachetype
, caching
, fd
;
2444 char *bp
, *ftp
, *user
, *pass
;
2445 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2447 CE ce
= &ct
->c_cefile
;
2448 static char *username
= NULL
;
2449 static char *password
= NULL
;
2453 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2459 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2470 if (!e
->eb_name
|| !e
->eb_site
) {
2471 content_error (NULL
, ct
, "missing %s parameter",
2472 e
->eb_name
? "site": "name");
2476 /* Get the buffer ready to go */
2478 buflen
= sizeof(buffer
);
2481 * Construct the query message for user
2483 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2489 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2495 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2496 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2501 if (e
->eb_size
> 0) {
2502 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2507 snprintf (bp
, buflen
, "? ");
2510 * Now, check the answer
2512 if (!read_yes_or_no_if_tty (buffer
))
2517 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2521 ruserpass (e
->eb_site
, &username
, &password
, 0);
2526 ce
->ce_unlink
= (*file
== NULL
);
2528 cachefile
[0] = '\0';
2529 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2530 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2531 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2532 if (*file
== NULL
) {
2539 ce
->ce_file
= add (*file
, NULL
);
2541 ce
->ce_file
= add (cachefile
, NULL
);
2544 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2545 adios(NULL
, "unable to create temporary file in %s",
2548 ce
->ce_file
= add (tempfile
, NULL
);
2551 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2552 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2557 int child_id
, i
, vecp
;
2561 vec
[vecp
++] = r1bindex (ftp
, '/');
2562 vec
[vecp
++] = e
->eb_site
;
2565 vec
[vecp
++] = e
->eb_dir
;
2566 vec
[vecp
++] = e
->eb_name
;
2567 vec
[vecp
++] = ce
->ce_file
,
2568 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2569 ? "ascii" : "binary";
2574 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2578 adios ("fork", "unable to");
2582 close (fileno (ce
->ce_fp
));
2584 fprintf (stderr
, "unable to exec ");
2590 if (pidXwait (child_id
, NULL
)) {
2591 username
= password
= NULL
;
2601 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2606 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2607 if ((fp
= fopen (cachefile
, "w"))) {
2609 FILE *gp
= ce
->ce_fp
;
2611 fseek (gp
, 0L, SEEK_SET
);
2613 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2615 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2616 advise ("openFTP", "fwrite");
2621 admonish (ce
->ce_file
, "error reading");
2622 (void) m_unlink (cachefile
);
2626 admonish (cachefile
, "error writing");
2627 (void) m_unlink (cachefile
);
2635 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2636 *file
= ce
->ce_file
;
2637 return fileno (ce
->ce_fp
);
2648 return init_encoding (ct
, openMail
);
2653 openMail (CT ct
, char **file
)
2655 int child_id
, fd
, i
, vecp
;
2657 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2658 struct exbody
*e
= ct
->c_ctexbody
;
2659 CE ce
= &ct
->c_cefile
;
2661 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2672 if (!e
->eb_server
) {
2673 content_error (NULL
, ct
, "missing server parameter");
2677 /* Get buffer ready to go */
2679 buflen
= sizeof(buffer
);
2681 /* Now, construct query message */
2682 snprintf (bp
, buflen
, "Retrieve content");
2688 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2694 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2696 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2698 /* Now, check answer */
2699 if (!read_yes_or_no_if_tty (buffer
))
2703 vec
[vecp
++] = r1bindex (mailproc
, '/');
2704 vec
[vecp
++] = e
->eb_server
;
2705 vec
[vecp
++] = "-subject";
2706 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2707 vec
[vecp
++] = "-body";
2708 vec
[vecp
++] = e
->eb_body
;
2711 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2715 advise ("fork", "unable to");
2719 execvp (mailproc
, vec
);
2720 fprintf (stderr
, "unable to exec ");
2726 if (pidXwait (child_id
, NULL
) == OK
)
2727 advise (NULL
, "request sent");
2731 if (*file
== NULL
) {
2733 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2734 adios(NULL
, "unable to create temporary file in %s",
2737 ce
->ce_file
= add (tempfile
, NULL
);
2740 ce
->ce_file
= add (*file
, NULL
);
2744 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2745 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2749 /* showproc is for mhshow and mhstore, though mhlist -debug
2750 * prints it, too. */
2752 free (ct
->c_showproc
);
2753 ct
->c_showproc
= add ("true", NULL
);
2755 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2756 *file
= ce
->ce_file
;
2757 return fileno (ce
->ce_fp
);
2768 return init_encoding (ct
, openURL
);
2773 openURL (CT ct
, char **file
)
2775 struct exbody
*e
= ct
->c_ctexbody
;
2776 CE ce
= &ct
->c_cefile
;
2777 char *urlprog
, *program
;
2778 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2779 int fd
, caching
, cachetype
;
2780 struct msgs_array args
= { 0, 0, NULL
};
2783 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2787 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2791 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2803 content_error(NULL
, ct
, "missing url parameter");
2807 ce
->ce_unlink
= (*file
== NULL
);
2809 cachefile
[0] = '\0';
2811 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2812 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2813 if (*file
== NULL
) {
2820 ce
->ce_file
= add(*file
, NULL
);
2822 ce
->ce_file
= add(cachefile
, NULL
);
2825 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2826 adios(NULL
, "unable to create temporary file in %s",
2829 ce
->ce_file
= add (tempfile
, NULL
);
2832 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2833 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2837 switch (child_id
= fork()) {
2839 adios ("fork", "unable to");
2843 argsplit_msgarg(&args
, urlprog
, &program
);
2844 app_msgarg(&args
, e
->eb_url
);
2845 app_msgarg(&args
, NULL
);
2846 dup2(fileno(ce
->ce_fp
), 1);
2847 close(fileno(ce
->ce_fp
));
2848 execvp(program
, args
.msgs
);
2849 fprintf(stderr
, "Unable to exec ");
2855 if (pidXwait(child_id
, NULL
)) {
2863 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2868 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2869 if ((fp
= fopen(cachefile
, "w"))) {
2871 FILE *gp
= ce
->ce_fp
;
2873 fseeko(gp
, 0, SEEK_SET
);
2875 while ((cc
= fread(buffer
, sizeof(*buffer
),
2876 sizeof(buffer
), gp
)) > 0)
2877 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2878 advise ("openURL", "fwrite");
2884 admonish(ce
->ce_file
, "error reading");
2885 (void) m_unlink (cachefile
);
2892 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2893 *file
= ce
->ce_file
;
2899 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2900 * has to be base64 decoded.
2903 readDigest (CT ct
, char *cp
)
2905 unsigned char *digest
;
2908 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2909 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2911 if (strlen ((char *) digest
) <= maxlen
) {
2912 memcpy (ct
->c_digest
, digest
, maxlen
);
2917 fprintf (stderr
, "MD5 digest=");
2918 for (i
= 0; i
< maxlen
; ++i
) {
2919 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2921 fprintf (stderr
, "\n");
2927 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2928 (int) strlen ((char *) digest
));
2938 /* Multipart parts might have content before the first subpart and/or
2939 after the last subpart that hasn't been stored anywhere else, so do
2942 get_leftover_mp_content (CT ct
, int before
/* or after */)
2944 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2946 int found_boundary
= 0;
2952 char *content
= NULL
;
2954 if (! m
) return NOTOK
;
2957 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2959 /* Isolate the beginning of this part to the beginning of the
2960 first subpart and save any content between them. */
2961 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2962 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2963 boundary
= concat ("--", m
->mp_start
, NULL
);
2965 struct part
*last_subpart
= NULL
;
2966 struct part
*subpart
;
2968 /* Go to the last subpart to get its end position. */
2969 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2970 last_subpart
= subpart
;
2973 if (last_subpart
== NULL
) return NOTOK
;
2975 /* Isolate the end of the last subpart to the end of this part
2976 and save any content between them. */
2977 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2978 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2979 boundary
= concat ("--", m
->mp_stop
, NULL
);
2982 /* Back up by 1 to pick up the newline. */
2983 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2985 /* Don't look beyond beginning of first subpart (before) or
2986 next part (after). */
2987 if (read
> max
) bufp
[read
-max
] = '\0';
2990 if (! strcmp (bufp
, boundary
)) {
2994 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3000 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3002 char *old_content
= content
;
3003 content
= concat (content
, bufp
, NULL
);
3007 ? concat ("\n", bufp
, NULL
)
3008 : concat (bufp
, NULL
);
3013 if (found_boundary
|| read
> max
) break;
3015 if (read
> max
) break;
3019 /* Skip the newline if that's all there is. */
3023 /* Remove trailing newline, except at EOF. */
3024 if ((before
|| ! feof (ct
->c_fp
)) &&
3025 (cp
= content
+ strlen (content
)) > content
&&
3030 if (strlen (content
) > 1) {
3032 m
->mp_content_before
= content
;
3034 m
->mp_content_after
= content
;
3049 ct_type_str (int type
) {
3051 case CT_APPLICATION
:
3052 return "application";
3068 return "unknown_type";
3074 ct_subtype_str (int type
, int subtype
) {
3076 case CT_APPLICATION
:
3078 case APPLICATION_OCTETS
:
3080 case APPLICATION_POSTSCRIPT
:
3081 return "postscript";
3083 return "unknown_app_subtype";
3087 case MESSAGE_RFC822
:
3089 case MESSAGE_PARTIAL
:
3091 case MESSAGE_EXTERNAL
:
3094 return "unknown_msg_subtype";
3100 case MULTI_ALTERNATE
:
3101 return "alternative";
3104 case MULTI_PARALLEL
:
3109 return "unknown_multipart_subtype";
3120 return "unknown_text_subtype";
3123 return "unknown_type";
3129 ct_str_type (const char *type
) {
3130 struct str2init
*s2i
;
3132 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3133 if (! strcasecmp (type
, s2i
->si_key
)) {
3137 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3146 ct_str_subtype (int type
, const char *subtype
) {
3150 case CT_APPLICATION
:
3151 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3152 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3156 return kv
->kv_value
;
3158 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3159 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3163 return kv
->kv_value
;
3165 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3166 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3170 return kv
->kv_value
;
3172 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3173 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3177 return kv
->kv_value
;
3184 /* Find the content type and InitFunc for the CT. */
3185 const struct str2init
*
3186 get_ct_init (int type
) {
3187 const struct str2init
*sp
;
3189 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3190 if (type
== sp
->si_val
) {
3199 ce_str (int encoding
) {
3204 return "quoted-printable";
3220 /* Find the content type and InitFunc for the content encoding method. */
3221 const struct str2init
*
3222 get_ce_method (const char *method
) {
3223 struct str2init
*sp
;
3225 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3226 if (! strcasecmp (method
, sp
->si_key
)) {
3235 * Parse a series of MIME attributes (or parameters) given a header as
3238 * Arguments include:
3240 * filename - Name of input file (for error messages)
3241 * fieldname - Name of field being processed
3242 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3243 * Updated to point to end of attributes when finished.
3244 * param_head - Pointer to head of parameter list
3245 * param_tail - Pointer to tail of parameter list
3246 * commentp - Pointer to header comment pointer (may be NULL)
3248 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3249 * DONE to indicate a benign error (minor parsing error, but the program
3254 parse_header_attrs (const char *filename
, const char *fieldname
,
3255 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3258 char *cp
= *header_attrp
;
3264 struct sectlist
*next
;
3270 struct sectlist
*sechead
;
3271 struct parmlist
*next
;
3272 } *pp
, *pp2
, *phead
= NULL
;
3274 while (*cp
== ';') {
3275 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3276 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3279 while (isspace ((unsigned char) *cp
))
3283 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3288 if (! suppress_extraneous_trailing_semicolon_warning
) {
3290 "extraneous trailing ';' in message %s's %s: "
3292 filename
, fieldname
);
3294 extraneous_trailing_semicolon
= 1;
3298 /* down case the attribute name */
3299 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3300 *dp
= tolower ((unsigned char) *dp
);
3302 for (up
= dp
; isspace ((unsigned char) *dp
);)
3304 if (dp
== cp
|| *dp
!= '=') {
3306 "invalid parameter in message %s's %s: "
3307 "field\n%*sparameter %s (error detected at offset %d)",
3308 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3313 * To handle RFC 2231, we have to deal with the following extensions:
3315 * name*=encoded-value
3316 * name*<N>=part-N-of-a-parameter-value
3317 * name*<N>*=encoded-part-N-of-a-parameter-value
3320 * If there's a * right before the equal sign, it's encoded.
3321 * If there's a * and one or more digits, then it's section N.
3323 * Remember we can have one or the other, or both. cp points to
3324 * beginning of name, up points past the last character in the
3328 for (vp
= cp
; vp
< up
; vp
++) {
3329 if (*vp
== '*' && vp
< up
- 1) {
3332 } else if (*vp
== '*' && vp
== up
- 1) {
3334 } else if (partial
) {
3335 if (isdigit((unsigned char) *vp
))
3336 index
= *vp
- '0' + index
* 10;
3338 advise (NULL
, "invalid parameter index in message %s's "
3339 "%s: field\n%*s(parameter %s)", filename
,
3340 fieldname
, strlen(invo_name
) + 2, "", cp
);
3349 * Break out the parameter name and value sections and allocate
3353 nameptr
= mh_xmalloc(len
+ 1);
3354 strncpy(nameptr
, cp
, len
);
3355 nameptr
[len
] = '\0';
3357 for (dp
++; isspace ((unsigned char) *dp
);)
3362 * Single quotes delimit the character set and language tag.
3363 * They are required on the first section (or a complete
3368 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3374 charset
= mh_xmalloc(len
+ 1);
3375 strncpy(charset
, dp
, len
);
3376 charset
[len
] = '\0';
3382 advise(NULL
, "missing charset in message %s's %s: "
3383 "field\n%*s(parameter %s)", filename
, fieldname
,
3384 strlen(invo_name
) + 2, "", nameptr
);
3390 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3397 lang
= mh_xmalloc(len
+ 1);
3398 strncpy(lang
, dp
, len
);
3405 advise(NULL
, "missing language tag in message %s's %s: "
3406 "field\n%*s(parameter %s)", filename
, fieldname
,
3407 strlen(invo_name
) + 2, "", nameptr
);
3418 * At this point vp should be pointing at the beginning
3419 * of the encoded value/section. Continue until we reach
3420 * the end or get whitespace. But first, calculate the
3421 * length so we can allocate the correct buffer size.
3424 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3426 if (*(vp
+ 1) == '\0' ||
3427 !isxdigit((unsigned char) *(vp
+ 1)) ||
3428 *(vp
+ 2) == '\0' ||
3429 !isxdigit((unsigned char) *(vp
+ 2))) {
3430 advise(NULL
, "invalid encoded sequence in message "
3431 "%s's %s: field\n%*s(parameter %s)",
3432 filename
, fieldname
, strlen(invo_name
) + 2,
3446 up
= valptr
= mh_xmalloc(len
+ 1);
3448 for (vp
= dp
; istoken(*vp
); vp
++) {
3450 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3461 * A "normal" string. If it's got a leading quote, then we
3462 * strip the quotes out. Otherwise go until we reach the end
3463 * or get whitespace. Note we scan it twice; once to get the
3464 * length, then the second time copies it into the destination
3471 for (cp
= dp
+ 1;;) {
3476 "invalid quoted-string in message %s's %s: "
3477 "field\n%*s(parameter %s)",
3478 filename
, fieldname
, strlen(invo_name
) + 2, "",
3501 for (cp
= dp
; istoken (*cp
); cp
++) {
3506 valptr
= mh_xmalloc(len
+ 1);
3510 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3518 strncpy(valptr
, cp
= dp
, len
);
3526 * If 'partial' is set, we don't allocate a parameter now. We
3527 * put it on the parameter linked list to be reassembled later.
3529 * "phead" points to a list of all parameters we need to reassemble.
3530 * Each parameter has a list of sections. We insert the sections in
3535 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3536 if (strcasecmp(nameptr
, pp
->name
) == 0)
3548 * Insert this into the section linked list
3556 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3557 sp
->next
= pp
->sechead
;
3560 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3561 if (sp2
->index
== sp
->index
) {
3562 advise (NULL
, "duplicate index (%d) in message "
3563 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3564 filename
, fieldname
, strlen(invo_name
) + 2, "",
3569 if (sp2
->index
< sp
->index
&&
3570 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3571 sp
->next
= sp2
->next
;
3578 advise(NULL
, "Internal error: cannot insert partial "
3579 "param in message %s's %s: field\n%*s(parameter %s)",
3580 filename
, fieldname
, strlen(invo_name
) + 2, "",
3588 * Save our charset and lang tags.
3591 if (index
== 0 && encoded
) {
3594 pp
->charset
= charset
;
3600 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3601 pm
->pm_charset
= charset
;
3605 while (isspace ((unsigned char) *cp
))
3609 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3615 * Now that we're done, reassemble all of the partial parameters.
3618 for (pp
= phead
; pp
!= NULL
; ) {
3622 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3623 if (sp
->index
!= pindex
++) {
3624 advise(NULL
, "missing section %d for parameter in "
3625 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3626 filename
, fieldname
, strlen(invo_name
) + 2, "",
3633 p
= q
= mh_xmalloc(tlen
+ 1);
3634 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3635 memcpy(q
, sp
->value
, sp
->len
);
3645 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3646 pm
->pm_charset
= pp
->charset
;
3647 pm
->pm_lang
= pp
->lang
;
3658 * Return the charset for a particular content type.
3662 content_charset (CT ct
) {
3663 char *ret_charset
= NULL
;
3665 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3667 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3672 * Create a string based on a list of output parameters. Assume that this
3673 * parameter string will be appended to an existing header, so start out
3674 * with the separator (;). Perform RFC 2231 encoding when necessary.
3678 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3680 char *paramout
= NULL
;
3681 char line
[CPERLIN
* 2], *q
;
3682 int curlen
, index
, cont
, encode
, i
;
3683 size_t valoff
, numchars
;
3685 while (params
!= NULL
) {
3691 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3694 if (strlen(params
->pm_name
) > CPERLIN
) {
3695 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3701 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3704 * Loop until we get a parameter that fits within a line. We
3705 * assume new lines start with a tab, so check our overflow based
3715 * At this point we're definitely continuing the line, so
3716 * be sure to include the parameter name and section index.
3719 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3720 params
->pm_name
, index
);
3723 * Both of these functions do a NUL termination
3727 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3728 numchars
, valoff
, index
);
3730 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3741 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3746 * "line" starts with a ;\n\t, so that doesn't count against
3747 * the length. But add 8 since it starts with a tab; that's
3748 * how we end up with 5.
3751 initialwidth
= strlen(line
) + 5;
3754 * At this point the line should be built, so add it to our
3755 * current output buffer.
3758 paramout
= add(line
, paramout
);
3762 * If this won't fit on the line, start a new one. Save room in
3763 * case we need a semicolon on the end
3766 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3778 * At this point, we're either finishing a contined parameter, or
3779 * we're working on a new one.
3783 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3784 params
->pm_name
, index
);
3786 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3791 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3792 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3794 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3795 strlen(params
->pm_value
+ valoff
), valoff
);
3803 paramout
= add(line
, paramout
);
3804 initialwidth
+= strlen(line
);
3806 params
= params
->pm_next
;
3810 *offsetout
= initialwidth
;
3816 * Calculate the size of a parameter.
3820 * pm - The parameter being output
3821 * index - If continuing the parameter, the index of the section
3823 * valueoff - The current offset into the parameter value that we're
3824 * working on (previous sections have consumed valueoff bytes).
3825 * encode - Set if we should perform encoding on this parameter section
3826 * (given that we're consuming bytesfit bytes).
3827 * cont - Set if the remaining data in value will not fit on a single
3828 * line and will need to be continued.
3829 * bytesfit - The number of bytes that we can consume from the parameter
3830 * value and still fit on a completely new line. The
3831 * calculation assumes the new line starts with a tab,
3832 * includes the parameter name and any encoding, and fits
3833 * within CPERLIN bytes. Will always be at least 1.
3837 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3840 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3841 size_t len
= 0, fit
= 0;
3842 int fitlimit
= 0, eightbit
, maxfit
;
3847 * Add up the length. First, start with the parameter name.
3850 len
= strlen(pm
->pm_name
);
3853 * Scan the parameter value and see if we need to do encoding for this
3857 eightbit
= contains8bit(start
, NULL
);
3860 * Determine if we need to encode this section. Encoding is necessary if:
3862 * - There are any 8-bit characters at all and we're on the first
3864 * - There are 8-bit characters within N bytes of our section start.
3865 * N is calculated based on the number of bytes it would take to
3866 * reach CPERLIN. Specifically:
3867 * 8 (starting tab) +
3868 * strlen(param name) +
3869 * 4 ('* for section marker, '=', opening/closing '"')
3871 * is the number of bytes used by everything that isn't part of the
3872 * value. So that gets subtracted from CPERLIN.
3875 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3876 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3877 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3881 len
++; /* Add in equal sign */
3885 * We're using maxfit as a marker for how many characters we can
3886 * fit into the line. Bump it by two because we're not using quotes
3893 * If we don't have a charset or language tag in this parameter,
3897 if (! pm
->pm_charset
) {
3898 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3899 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3900 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3901 "local character set is US-ASCII", pm
->pm_name
);
3904 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3906 len
++; /* For the encoding marker */
3909 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3914 * We know we definitely need to include an index. maxfit already
3915 * includes the section marker.
3917 len
+= strlen(indexchar
);
3919 for (p
= start
; *p
!= '\0'; p
++) {
3920 if (isparamencode(*p
)) {
3928 * Just so there's no confusion: maxfit is counting OUTPUT
3929 * characters (post-encoding). fit is counting INPUT characters.
3931 if (! fitlimit
&& maxfit
>= 0)
3933 else if (! fitlimit
)
3938 * Calculate the string length, but add room for quoting \
3939 * and " if necessary. Also account for quotes at beginning
3942 for (p
= start
; *p
!= '\0'; p
++) {
3953 if (! fitlimit
&& maxfit
>= 0)
3955 else if (! fitlimit
)
3972 * Output an encoded parameter string.
3976 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3977 size_t valueoff
, int index
)
3979 size_t outlen
= 0, n
;
3980 char *endptr
= output
+ len
, *p
;
3983 * First, output the marker for an encoded string.
3991 * If the index is 0, output the character set and language tag.
3992 * If theses were NULL, they should have already been filled in
3997 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
4001 if (output
> endptr
) {
4002 advise(NULL
, "Internal error: parameter buffer overflow");
4008 * Copy over the value, encoding if necessary
4011 p
= pm
->pm_value
+ valueoff
;
4012 while (valuelen
-- > 0) {
4013 if (isparamencode(*p
)) {
4014 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4021 if (output
> endptr
) {
4022 advise(NULL
, "Internal error: parameter buffer overflow");
4033 * Output a "normal" parameter, without encoding. Be sure to escape
4034 * quotes and backslashes if necessary.
4038 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4042 char *endptr
= output
+ len
, *p
;
4048 p
= pm
->pm_value
+ valueoff
;
4050 while (valuelen
-- > 0) {
4060 if (output
> endptr
) {
4061 advise(NULL
, "Internal error: parameter buffer overflow");
4066 if (output
- 2 > endptr
) {
4067 advise(NULL
, "Internal error: parameter buffer overflow");
4078 * Add a parameter to the parameter linked list
4082 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4087 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4088 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4091 (*last
)->pm_next
= pm
;
4102 * Either replace a current parameter with a new value, or add the parameter
4103 * to the parameter linked list.
4107 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4111 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4112 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4114 * If nocopy is set, it's assumed that we own both name
4115 * and value. We don't need name, so we discard it now.
4120 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4125 return add_param(first
, last
, name
, value
, nocopy
);
4129 * Retrieve a parameter value from a parameter linked list. If the parameter
4130 * value needs converted to the local character set, do that now.
4134 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4136 while (first
!= NULL
) {
4137 if (strcasecmp(name
, first
->pm_name
) == 0) {
4139 return first
->pm_value
;
4140 return getcpy(get_param_value(first
, replace
));
4142 first
= first
->pm_next
;
4149 * Return a parameter value, converting to the local character set if
4153 char *get_param_value(PM pm
, char replace
)
4155 static char buffer
[4096]; /* I hope no parameters are larger */
4156 size_t bufsize
= sizeof(buffer
);
4161 ICONV_CONST
char *p
;
4162 #else /* HAVE_ICONV */
4164 #endif /* HAVE_ICONV */
4169 * If we don't have a character set indicated, it's assumed to be
4170 * US-ASCII. If it matches our character set, we don't need to convert
4174 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4175 strlen(pm
->pm_charset
))) {
4176 return pm
->pm_value
;
4180 * In this case, we need to convert. If we have iconv support, use
4181 * that. Otherwise, go through and simply replace every non-ASCII
4182 * character with the substitution character.
4187 bufsize
= sizeof(buffer
);
4188 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4190 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4191 if (cd
== (iconv_t
) -1) {
4195 inbytes
= strlen(pm
->pm_value
);
4199 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4200 if (errno
!= EILSEQ
) {
4205 * Reset shift state, substitute our character,
4206 * try to restart conversion.
4209 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4222 for (++p
, --inbytes
;
4223 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4242 #endif /* HAVE_ICONV */
4245 * Take everything non-ASCII and substituite the replacement character
4249 bufsize
= sizeof(buffer
);
4250 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4251 /* FIXME: !iscntrl should perhaps be isprint as that allows all
4252 * classes bar cntrl, whereas the cntrl class can include those
4253 * in space and blank.
4254 * http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html */
4255 if (isascii((unsigned char) *p
) && !iscntrl((unsigned char) *p
))