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
== '<') {
723 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
724 advise (NULL
, "invalid ID in message %s", ct
->c_file
);
730 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
736 while (isspace ((unsigned char) *cp
))
741 * Get any [Content-Description] given in buffer.
743 if (magic
&& *cp
== '[') {
745 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
749 advise (NULL
, "invalid description in message %s", ct
->c_file
);
757 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
763 while (isspace ((unsigned char) *cp
))
768 * Get any {Content-Disposition} given in buffer.
770 if (magic
&& *cp
== '{') {
772 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
776 advise (NULL
, "invalid disposition in message %s", ct
->c_file
);
784 if (get_dispo(cp
, ct
, 1) != OK
)
790 while (isspace ((unsigned char) *cp
))
795 * Get any extension directives (right now just the content transfer
796 * encoding, but maybe others) that we care about.
799 if (magic
&& *cp
== '*') {
801 * See if it's a CTE we match on
806 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
810 advise (NULL
, "invalid null transfer encoding specification");
817 ct
->c_reqencoding
= CE_UNKNOWN
;
819 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
820 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
821 ct
->c_reqencoding
= kv
->kv_value
;
826 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
827 advise (NULL
, "invalid CTE specification: \"%s\"", dp
);
831 while (isspace ((unsigned char) *cp
))
836 * Check if anything is left over
840 ci
->ci_magic
= add (cp
, NULL
);
842 /* If there is a Content-Disposition header and it doesn't
843 have a *filename=, extract it from the magic contents.
844 The r1bindex call skips any leading directory
846 if (ct
->c_dispo_type
&&
847 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
848 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
849 r1bindex(ci
->ci_magic
, '/'), 0);
854 "extraneous information in message %s's %s: field\n%*s(%s)",
855 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
863 * Parse out a Content-Disposition header. A lot of this is cribbed from
867 get_dispo (char *cp
, CT ct
, int buildflag
)
869 char *dp
, *dispoheader
;
874 * Save the whole copy of the Content-Disposition header, unless we're
875 * processing a mhbuild directive. A NULL c_dispo will be a flag to
876 * mhbuild that the disposition header needs to be generated at that
880 dispoheader
= cp
= add(cp
, NULL
);
882 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
885 /* change newlines to spaces */
886 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
889 /* trim trailing spaces */
890 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
891 if (!isspace ((unsigned char) *dp
))
896 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
898 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
904 for (dp
= cp
; istoken (*dp
); dp
++)
907 ct
->c_dispo_type
= add (cp
, NULL
); /* store disposition type */
910 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
913 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
914 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
916 if (status
== NOTOK
) {
922 "extraneous information in message %s's %s: field\n%*s(%s)",
923 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
929 ct
->c_dispo
= dispoheader
;
936 get_comment (const char *filename
, const char *fieldname
, char **ap
,
941 char c
, buffer
[BUFSIZ
], *dp
;
951 advise (NULL
, "invalid comment in message %s's %s: field",
952 filename
, fieldname
);
957 if ((c
= *cp
++) == '\0')
980 if ((dp
= *commentp
)) {
981 *commentp
= concat (dp
, " ", buffer
, NULL
);
984 *commentp
= add (buffer
, NULL
);
988 while (isspace ((unsigned char) *cp
))
999 * Handles content types audio, image, and video.
1000 * There's not much to do right here.
1008 return OK
; /* not much to do here */
1019 char buffer
[BUFSIZ
];
1024 CI ci
= &ct
->c_ctinfo
;
1026 /* check for missing subtype */
1027 if (!*ci
->ci_subtype
)
1028 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1031 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1033 /* allocate text character set structure */
1035 ct
->c_ctparams
= (void *) t
;
1037 /* scan for charset parameter */
1038 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1039 if (!strcasecmp (pm
->pm_name
, "charset"))
1042 /* check if content specified a character set */
1044 chset
= pm
->pm_value
;
1045 t
->tx_charset
= CHARSET_SPECIFIED
;
1047 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1051 * If we can not handle character set natively,
1052 * then check profile for string to modify the
1053 * terminal or display method.
1055 * termproc is for mhshow, though mhlist -debug prints it, too.
1057 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1058 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1059 if ((cp
= context_find (buffer
)))
1060 ct
->c_termproc
= mh_xstrdup(cp
);
1072 InitMultiPart (CT ct
)
1082 struct multipart
*m
;
1083 struct part
*part
, **next
;
1084 CI ci
= &ct
->c_ctinfo
;
1089 * The encoding for multipart messages must be either
1090 * 7bit, 8bit, or binary (per RFC2045).
1092 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1093 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1094 /* Copy the Content-Transfer-Encoding header field body so we can
1095 remove any trailing whitespace and leading blanks from it. */
1096 char *cte
= add (ct
->c_celine
? ct
->c_celine
: "(null)", NULL
);
1098 bp
= cte
+ strlen (cte
) - 1;
1099 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1100 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1103 "\"%s/%s\" type in message %s must be encoded in\n"
1104 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1105 "mhfixmsg -fixcte can fix it, or\n"
1106 "manually edit the file and change the \"%s\"\n"
1107 "Content-Transfer-Encoding to one of those. For now",
1108 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1115 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1118 * Check for "boundary" parameter, which is
1119 * required for multipart messages.
1122 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1123 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1129 /* complain if boundary parameter is missing */
1132 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1133 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1137 /* allocate primary structure for multipart info */
1139 ct
->c_ctparams
= (void *) m
;
1141 /* check if boundary parameter contains only whitespace characters */
1142 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1145 advise (NULL
, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1146 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1150 /* remove trailing whitespace from boundary parameter */
1151 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1152 if (!isspace ((unsigned char) *dp
))
1156 /* record boundary separators */
1157 m
->mp_start
= concat (bp
, "\n", NULL
);
1158 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1160 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1161 advise (ct
->c_file
, "unable to open for reading");
1165 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1167 next
= &m
->mp_parts
;
1171 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1176 if (bufp
[0] != '-' || bufp
[1] != '-')
1179 if (strcmp (bufp
+ 2, m
->mp_start
))
1184 next
= &part
->mp_next
;
1186 if (!(p
= get_content (fp
, ct
->c_file
,
1187 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1195 fseek (fp
, pos
, SEEK_SET
);
1198 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1202 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1203 if (p
->c_end
< p
->c_begin
)
1204 p
->c_begin
= p
->c_end
;
1209 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1214 if (! suppress_bogus_mp_content_warning
) {
1215 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1217 bogus_mp_content
= 1;
1219 if (!inout
&& part
) {
1221 p
->c_end
= ct
->c_end
;
1223 if (p
->c_begin
>= p
->c_end
) {
1224 for (next
= &m
->mp_parts
; *next
!= part
;
1225 next
= &((*next
)->mp_next
))
1234 /* reverse the order of the parts for multipart/alternative */
1235 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1241 * label all subparts with part number, and
1242 * then initialize the content of the subpart.
1247 char partnam
[BUFSIZ
];
1250 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1251 pp
= partnam
+ strlen (partnam
);
1256 for (part
= m
->mp_parts
, partnum
= 1; part
;
1257 part
= part
->mp_next
, partnum
++) {
1260 sprintf (pp
, "%d", partnum
);
1261 p
->c_partno
= add (partnam
, NULL
);
1263 /* initialize the content of the subparts */
1264 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1273 get_leftover_mp_content (ct
, 1);
1274 get_leftover_mp_content (ct
, 0);
1284 * reverse the order of the parts of a multipart/alternative,
1285 * presumably to put the "most favored" alternative first, for
1286 * ease of choosing/displaying it later on. from a mail message on
1287 * nmh-workers, from kenh:
1288 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1289 * see code in mhn that did the same thing... Acccording to the RCS
1290 * logs, that code was around from the initial checkin of mhn.c by
1291 * John Romine in 1992, which is as far back as we have."
1294 reverse_parts (CT ct
)
1296 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1300 /* Reverse the order of its parts by walking the mp_parts list
1301 and pushing each node to the front. */
1302 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1303 next
= part
->mp_next
;
1304 part
->mp_next
= m
->mp_parts
;
1310 move_preferred_part (CT ct
, char *type
, char *subtype
)
1312 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1313 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1317 /* move the matching part(s) to the head of the list: walk the
1318 * list of parts, move matching parts to a new list (maintaining
1319 * their order), and finally, concatenate the old list onto the
1326 head
->mp_next
= m
->mp_parts
;
1327 nhead
->mp_next
= NULL
;
1331 part
= head
->mp_next
;
1332 while (part
!= NULL
) {
1333 ci
= &part
->mp_part
->c_ctinfo
;
1334 if (!strcasecmp(ci
->ci_type
, type
) &&
1335 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1336 prev
->mp_next
= part
->mp_next
;
1337 part
->mp_next
= NULL
;
1338 ntail
->mp_next
= part
;
1340 part
= prev
->mp_next
;
1343 part
= prev
->mp_next
;
1346 ntail
->mp_next
= head
->mp_next
;
1347 m
->mp_parts
= nhead
->mp_next
;
1352 * move parts that match the user's preferences (-prefer) to the head
1353 * of the line. process preferences in reverse so first one given
1354 * ends up first in line
1360 for (i
= npreferred
-1; i
>= 0; i
--)
1361 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1366 /* parse_mime() arranges alternates in reverse (priority) order. This
1367 function can be used to reverse them back. This will put, for
1368 example, a text/plain part before a text/html part in a
1369 multipart/alternative part, for example, where it belongs. */
1371 reverse_alternative_parts (CT ct
) {
1372 if (ct
->c_type
== CT_MULTIPART
) {
1373 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1376 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1380 /* And call recursively on each part of a multipart. */
1381 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1382 reverse_alternative_parts (part
->mp_part
);
1395 CI ci
= &ct
->c_ctinfo
;
1397 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1399 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1400 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1404 /* check for missing subtype */
1405 if (!*ci
->ci_subtype
)
1406 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1409 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1411 switch (ct
->c_subtype
) {
1412 case MESSAGE_RFC822
:
1415 case MESSAGE_PARTIAL
:
1421 ct
->c_ctparams
= (void *) p
;
1423 /* scan for parameters "id", "number", and "total" */
1424 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1425 if (!strcasecmp (pm
->pm_name
, "id")) {
1426 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1429 if (!strcasecmp (pm
->pm_name
, "number")) {
1430 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1431 || p
->pm_partno
< 1) {
1434 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1435 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1436 ct
->c_file
, TYPE_FIELD
);
1441 if (!strcasecmp (pm
->pm_name
, "total")) {
1442 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1451 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1453 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1454 ci
->ci_type
, ci
->ci_subtype
,
1455 ct
->c_file
, TYPE_FIELD
);
1461 case MESSAGE_EXTERNAL
:
1469 ct
->c_ctparams
= (void *) e
;
1472 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1473 advise (ct
->c_file
, "unable to open for reading");
1477 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1479 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1487 p
->c_ceopenfnx
= NULL
;
1488 if ((exresult
= params_external (ct
, 0)) != NOTOK
1489 && p
->c_ceopenfnx
== openMail
) {
1493 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1495 content_error (NULL
, ct
,
1496 "empty body for access-type=mail-server");
1500 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1501 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1503 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1505 adios ("failed", "fread");
1508 adios (NULL
, "unexpected EOF from fread");
1511 bp
+= cc
, size
-= cc
;
1518 p
->c_end
= p
->c_begin
;
1523 if (exresult
== NOTOK
)
1525 if (e
->eb_flags
== NOTOK
)
1528 switch (p
->c_type
) {
1533 if (p
->c_subtype
!= MESSAGE_RFC822
)
1537 e
->eb_partno
= ct
->c_partno
;
1539 (*p
->c_ctinitfnx
) (p
);
1554 params_external (CT ct
, int composing
)
1557 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1558 CI ci
= &ct
->c_ctinfo
;
1560 ct
->c_ceopenfnx
= NULL
;
1561 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1562 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1563 struct str2init
*s2i
;
1564 CT p
= e
->eb_content
;
1566 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1567 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1570 e
->eb_access
= pm
->pm_value
;
1571 e
->eb_flags
= NOTOK
;
1572 p
->c_encoding
= CE_EXTERNAL
;
1575 e
->eb_access
= s2i
->si_key
;
1576 e
->eb_flags
= s2i
->si_val
;
1577 p
->c_encoding
= CE_EXTERNAL
;
1579 /* Call the Init function for this external type */
1580 if ((*s2i
->si_init
)(p
) == NOTOK
)
1584 if (!strcasecmp (pm
->pm_name
, "name")) {
1585 e
->eb_name
= pm
->pm_value
;
1588 if (!strcasecmp (pm
->pm_name
, "permission")) {
1589 e
->eb_permission
= pm
->pm_value
;
1592 if (!strcasecmp (pm
->pm_name
, "site")) {
1593 e
->eb_site
= pm
->pm_value
;
1596 if (!strcasecmp (pm
->pm_name
, "directory")) {
1597 e
->eb_dir
= pm
->pm_value
;
1600 if (!strcasecmp (pm
->pm_name
, "mode")) {
1601 e
->eb_mode
= pm
->pm_value
;
1604 if (!strcasecmp (pm
->pm_name
, "size")) {
1605 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1608 if (!strcasecmp (pm
->pm_name
, "server")) {
1609 e
->eb_server
= pm
->pm_value
;
1612 if (!strcasecmp (pm
->pm_name
, "subject")) {
1613 e
->eb_subject
= pm
->pm_value
;
1616 if (!strcasecmp (pm
->pm_name
, "url")) {
1618 * According to RFC 2017, we have to remove all whitespace from
1622 char *u
, *p
= pm
->pm_value
;
1623 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1625 for (; *p
!= '\0'; p
++) {
1626 if (! isspace((unsigned char) *p
))
1633 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1634 e
->eb_body
= getcpy (pm
->pm_value
);
1639 if (!e
->eb_access
) {
1641 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1642 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1655 InitApplication (CT ct
)
1657 CI ci
= &ct
->c_ctinfo
;
1660 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1667 * TRANSFER ENCODINGS
1671 init_encoding (CT ct
, OpenCEFunc openfnx
)
1673 ct
->c_ceopenfnx
= openfnx
;
1674 ct
->c_ceclosefnx
= close_encoding
;
1675 ct
->c_cesizefnx
= size_encoding
;
1682 close_encoding (CT ct
)
1684 CE ce
= &ct
->c_cefile
;
1693 static unsigned long
1694 size_encoding (CT ct
)
1699 CE ce
= &ct
->c_cefile
;
1702 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1703 return (long) st
.st_size
;
1706 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1707 return (long) st
.st_size
;
1711 if (ct
->c_encoding
== CE_EXTERNAL
)
1712 return (ct
->c_end
- ct
->c_begin
);
1715 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1716 return (ct
->c_end
- ct
->c_begin
);
1718 if (fstat (fd
, &st
) != NOTOK
)
1719 size
= (long) st
.st_size
;
1723 (*ct
->c_ceclosefnx
) (ct
);
1735 return init_encoding (ct
, openBase64
);
1740 openBase64 (CT ct
, char **file
)
1743 int fd
, own_ct_fp
= 0;
1744 char *cp
, *buffer
= NULL
;
1745 /* sbeck -- handle suffixes */
1747 CE ce
= &ct
->c_cefile
;
1748 unsigned char *decoded
;
1750 unsigned char digest
[16];
1753 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1758 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1759 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1765 if (*file
== NULL
) {
1768 ce
->ce_file
= add (*file
, NULL
);
1772 /* sbeck@cise.ufl.edu -- handle suffixes */
1774 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1775 if (ce
->ce_unlink
) {
1776 /* Create temporary file with filename extension. */
1777 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1778 adios(NULL
, "unable to create temporary file in %s",
1782 ce
->ce_file
= add (cp
, ce
->ce_file
);
1784 } else if (*file
== NULL
) {
1786 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1787 adios(NULL
, "unable to create temporary file in %s",
1790 ce
->ce_file
= add (tempfile
, NULL
);
1793 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1794 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1798 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1799 adios (NULL
, "internal error(1)");
1801 buffer
= mh_xmalloc (len
+ 1);
1804 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1805 content_error (ct
->c_file
, ct
, "unable to open for reading");
1811 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1814 switch (cc
= read (fd
, cp
, len
)) {
1816 content_error (ct
->c_file
, ct
, "error reading from");
1820 content_error (NULL
, ct
, "premature eof");
1831 /* decodeBase64() requires null-terminated input. */
1834 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1835 ct
->c_digested
? digest
: NULL
) == OK
) {
1837 unsigned char *decoded_p
= decoded
;
1838 for (i
= 0; i
< decoded_len
; ++i
) {
1839 putc (*decoded_p
++, ce
->ce_fp
);
1842 if (ferror (ce
->ce_fp
)) {
1843 content_error (ce
->ce_file
, ct
, "error writing to");
1847 if (ct
->c_digested
) {
1848 if (memcmp(digest
, ct
->c_digest
,
1849 sizeof(digest
) / sizeof(digest
[0]))) {
1850 content_error (NULL
, ct
,
1851 "content integrity suspect (digest mismatch) -- continuing");
1854 fprintf (stderr
, "content integrity confirmed\n");
1862 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1864 if (fflush (ce
->ce_fp
)) {
1865 content_error (ce
->ce_file
, ct
, "error writing to");
1869 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1872 *file
= ce
->ce_file
;
1878 return fileno (ce
->ce_fp
);
1885 free_encoding (ct
, 0);
1895 static char hex2nib
[0x80] = {
1896 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1897 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1898 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1899 0x00, 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, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1903 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1909 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1910 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1911 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1918 return init_encoding (ct
, openQuoted
);
1923 openQuoted (CT ct
, char **file
)
1925 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1931 CE ce
= &ct
->c_cefile
;
1932 /* sbeck -- handle suffixes */
1937 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1942 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1943 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1949 if (*file
== NULL
) {
1952 ce
->ce_file
= add (*file
, NULL
);
1956 /* sbeck@cise.ufl.edu -- handle suffixes */
1958 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1959 if (ce
->ce_unlink
) {
1960 /* Create temporary file with filename extension. */
1961 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1962 adios(NULL
, "unable to create temporary file in %s",
1966 ce
->ce_file
= add (cp
, ce
->ce_file
);
1968 } else if (*file
== NULL
) {
1970 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1971 adios(NULL
, "unable to create temporary file in %s",
1974 ce
->ce_file
= add (tempfile
, NULL
);
1977 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1978 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1982 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1983 adios (NULL
, "internal error(2)");
1986 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1987 content_error (ct
->c_file
, ct
, "unable to open for reading");
1993 if ((digested
= ct
->c_digested
))
1994 MD5Init (&mdContext
);
2001 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2003 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2004 content_error (NULL
, ct
, "premature eof");
2008 if ((cc
= gotlen
) > len
)
2012 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2013 if (!isspace ((unsigned char) *ep
))
2017 for (; cp
< ep
; cp
++) {
2019 /* in an escape sequence */
2021 /* at byte 1 of an escape sequence */
2022 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2023 /* next is byte 2 */
2026 /* at byte 2 of an escape sequence */
2028 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2029 putc (mask
, ce
->ce_fp
);
2031 MD5Update (&mdContext
, &mask
, 1);
2032 if (ferror (ce
->ce_fp
)) {
2033 content_error (ce
->ce_file
, ct
, "error writing to");
2036 /* finished escape sequence; next may be literal or a new
2037 * escape sequence */
2040 /* on to next byte */
2044 /* not in an escape sequence */
2046 /* starting an escape sequence, or invalid '='? */
2047 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2048 /* "=\n" soft line break, eat the \n */
2052 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2053 /* We don't have 2 bytes left, so this is an invalid
2054 * escape sequence; just show the raw bytes (below). */
2055 } else if (isxdigit ((unsigned char) cp
[1]) &&
2056 isxdigit ((unsigned char) cp
[2])) {
2057 /* Next 2 bytes are hex digits, making this a valid escape
2058 * sequence; let's decode it (above). */
2062 /* One or both of the next 2 is out of range, making this
2063 * an invalid escape sequence; just show the raw bytes
2068 /* Just show the raw byte. */
2069 putc (*cp
, ce
->ce_fp
);
2072 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2074 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2077 if (ferror (ce
->ce_fp
)) {
2078 content_error (ce
->ce_file
, ct
, "error writing to");
2084 content_error (NULL
, ct
,
2085 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2089 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2091 if (fflush (ce
->ce_fp
)) {
2092 content_error (ce
->ce_file
, ct
, "error writing to");
2097 unsigned char digest
[16];
2099 MD5Final (digest
, &mdContext
);
2100 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2101 sizeof(digest
) / sizeof(digest
[0])))
2102 content_error (NULL
, ct
,
2103 "content integrity suspect (digest mismatch) -- continuing");
2106 fprintf (stderr
, "content integrity confirmed\n");
2109 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2112 *file
= ce
->ce_file
;
2118 return fileno (ce
->ce_fp
);
2121 free_encoding (ct
, 0);
2138 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2141 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2147 open7Bit (CT ct
, char **file
)
2149 int cc
, fd
, len
, own_ct_fp
= 0;
2150 char buffer
[BUFSIZ
];
2151 /* sbeck -- handle suffixes */
2154 CE ce
= &ct
->c_cefile
;
2157 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2162 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2163 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2169 if (*file
== NULL
) {
2172 ce
->ce_file
= add (*file
, NULL
);
2176 /* sbeck@cise.ufl.edu -- handle suffixes */
2178 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2179 if (ce
->ce_unlink
) {
2180 /* Create temporary file with filename extension. */
2181 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2182 adios(NULL
, "unable to create temporary file in %s",
2186 ce
->ce_file
= add (cp
, ce
->ce_file
);
2188 } else if (*file
== NULL
) {
2190 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2191 adios(NULL
, "unable to create temporary file in %s",
2194 ce
->ce_file
= add (tempfile
, NULL
);
2197 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2198 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2202 if (ct
->c_type
== CT_MULTIPART
) {
2203 CI ci
= &ct
->c_ctinfo
;
2207 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2208 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2209 + 1 + strlen (ci
->ci_subtype
);
2210 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2213 fputs (buffer
, ce
->ce_fp
);
2217 if (ci
->ci_comment
) {
2218 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2219 fputs ("\n\t", ce
->ce_fp
);
2223 putc (' ', ce
->ce_fp
);
2226 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2229 fprintf (ce
->ce_fp
, "\n");
2231 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2233 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2235 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2236 fprintf (ce
->ce_fp
, "\n");
2239 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2240 adios (NULL
, "internal error(3)");
2243 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2244 content_error (ct
->c_file
, ct
, "unable to open for reading");
2250 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2252 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2254 content_error (ct
->c_file
, ct
, "error reading from");
2258 content_error (NULL
, ct
, "premature eof");
2266 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2267 advise ("open7Bit", "fwrite");
2269 if (ferror (ce
->ce_fp
)) {
2270 content_error (ce
->ce_file
, ct
, "error writing to");
2275 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2277 if (fflush (ce
->ce_fp
)) {
2278 content_error (ce
->ce_file
, ct
, "error writing to");
2282 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2285 *file
= ce
->ce_file
;
2290 return fileno (ce
->ce_fp
);
2293 free_encoding (ct
, 0);
2307 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2309 char cachefile
[BUFSIZ
];
2312 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2317 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2318 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2324 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2325 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2326 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2327 ce
->ce_file
= mh_xstrdup(cachefile
);
2331 admonish (cachefile
, "unable to fopen for reading");
2334 *fd
= fileno (ce
->ce_fp
);
2338 *file
= ce
->ce_file
;
2339 *fd
= fileno (ce
->ce_fp
);
2350 return init_encoding (ct
, openFile
);
2355 openFile (CT ct
, char **file
)
2358 char cachefile
[BUFSIZ
];
2359 struct exbody
*e
= ct
->c_ctexbody
;
2360 CE ce
= &ct
->c_cefile
;
2362 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2374 content_error (NULL
, ct
, "missing name parameter");
2378 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2381 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2382 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2386 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2387 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2388 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2392 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2393 if ((fp
= fopen (cachefile
, "w"))) {
2395 char buffer
[BUFSIZ
];
2396 FILE *gp
= ce
->ce_fp
;
2398 fseek (gp
, 0L, SEEK_SET
);
2400 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2402 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2403 advise ("openFile", "fwrite");
2408 admonish (ce
->ce_file
, "error reading");
2409 (void) m_unlink (cachefile
);
2413 admonish (cachefile
, "error writing");
2414 (void) m_unlink (cachefile
);
2421 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2422 *file
= ce
->ce_file
;
2423 return fileno (ce
->ce_fp
);
2433 return init_encoding (ct
, openFTP
);
2438 openFTP (CT ct
, char **file
)
2440 int cachetype
, caching
, fd
;
2442 char *bp
, *ftp
, *user
, *pass
;
2443 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2445 CE ce
= &ct
->c_cefile
;
2446 static char *username
= NULL
;
2447 static char *password
= NULL
;
2451 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2457 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2468 if (!e
->eb_name
|| !e
->eb_site
) {
2469 content_error (NULL
, ct
, "missing %s parameter",
2470 e
->eb_name
? "site": "name");
2474 /* Get the buffer ready to go */
2476 buflen
= sizeof(buffer
);
2479 * Construct the query message for user
2481 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2487 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2493 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2494 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2499 if (e
->eb_size
> 0) {
2500 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2505 snprintf (bp
, buflen
, "? ");
2508 * Now, check the answer
2510 if (!read_yes_or_no_if_tty (buffer
))
2515 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2519 ruserpass (e
->eb_site
, &username
, &password
, 0);
2524 ce
->ce_unlink
= (*file
== NULL
);
2526 cachefile
[0] = '\0';
2527 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2528 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2529 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2530 if (*file
== NULL
) {
2537 ce
->ce_file
= add (*file
, NULL
);
2539 ce
->ce_file
= add (cachefile
, NULL
);
2542 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2543 adios(NULL
, "unable to create temporary file in %s",
2546 ce
->ce_file
= add (tempfile
, NULL
);
2549 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2550 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2555 int child_id
, i
, vecp
;
2559 vec
[vecp
++] = r1bindex (ftp
, '/');
2560 vec
[vecp
++] = e
->eb_site
;
2563 vec
[vecp
++] = e
->eb_dir
;
2564 vec
[vecp
++] = e
->eb_name
;
2565 vec
[vecp
++] = ce
->ce_file
,
2566 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2567 ? "ascii" : "binary";
2572 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2576 adios ("fork", "unable to");
2580 close (fileno (ce
->ce_fp
));
2582 fprintf (stderr
, "unable to exec ");
2588 if (pidXwait (child_id
, NULL
)) {
2589 username
= password
= NULL
;
2599 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2604 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2605 if ((fp
= fopen (cachefile
, "w"))) {
2607 FILE *gp
= ce
->ce_fp
;
2609 fseek (gp
, 0L, SEEK_SET
);
2611 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2613 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2614 advise ("openFTP", "fwrite");
2619 admonish (ce
->ce_file
, "error reading");
2620 (void) m_unlink (cachefile
);
2624 admonish (cachefile
, "error writing");
2625 (void) m_unlink (cachefile
);
2633 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2634 *file
= ce
->ce_file
;
2635 return fileno (ce
->ce_fp
);
2646 return init_encoding (ct
, openMail
);
2651 openMail (CT ct
, char **file
)
2653 int child_id
, fd
, i
, vecp
;
2655 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2656 struct exbody
*e
= ct
->c_ctexbody
;
2657 CE ce
= &ct
->c_cefile
;
2659 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2670 if (!e
->eb_server
) {
2671 content_error (NULL
, ct
, "missing server parameter");
2675 /* Get buffer ready to go */
2677 buflen
= sizeof(buffer
);
2679 /* Now, construct query message */
2680 snprintf (bp
, buflen
, "Retrieve content");
2686 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2692 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2694 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2696 /* Now, check answer */
2697 if (!read_yes_or_no_if_tty (buffer
))
2701 vec
[vecp
++] = r1bindex (mailproc
, '/');
2702 vec
[vecp
++] = e
->eb_server
;
2703 vec
[vecp
++] = "-subject";
2704 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2705 vec
[vecp
++] = "-body";
2706 vec
[vecp
++] = e
->eb_body
;
2709 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2713 advise ("fork", "unable to");
2717 execvp (mailproc
, vec
);
2718 fprintf (stderr
, "unable to exec ");
2724 if (pidXwait (child_id
, NULL
) == OK
)
2725 advise (NULL
, "request sent");
2729 if (*file
== NULL
) {
2731 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2732 adios(NULL
, "unable to create temporary file in %s",
2735 ce
->ce_file
= add (tempfile
, NULL
);
2738 ce
->ce_file
= add (*file
, NULL
);
2742 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2743 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2747 /* showproc is for mhshow and mhstore, though mhlist -debug
2748 * prints it, too. */
2749 mh_xfree(ct
->c_showproc
);
2750 ct
->c_showproc
= add ("true", NULL
);
2752 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2753 *file
= ce
->ce_file
;
2754 return fileno (ce
->ce_fp
);
2765 return init_encoding (ct
, openURL
);
2770 openURL (CT ct
, char **file
)
2772 struct exbody
*e
= ct
->c_ctexbody
;
2773 CE ce
= &ct
->c_cefile
;
2774 char *urlprog
, *program
;
2775 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2776 int fd
, caching
, cachetype
;
2777 struct msgs_array args
= { 0, 0, NULL
};
2780 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2784 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2788 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2800 content_error(NULL
, ct
, "missing url parameter");
2804 ce
->ce_unlink
= (*file
== NULL
);
2806 cachefile
[0] = '\0';
2808 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2809 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2810 if (*file
== NULL
) {
2817 ce
->ce_file
= add(*file
, NULL
);
2819 ce
->ce_file
= add(cachefile
, NULL
);
2822 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2823 adios(NULL
, "unable to create temporary file in %s",
2826 ce
->ce_file
= add (tempfile
, NULL
);
2829 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2830 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2834 switch (child_id
= fork()) {
2836 adios ("fork", "unable to");
2840 argsplit_msgarg(&args
, urlprog
, &program
);
2841 app_msgarg(&args
, e
->eb_url
);
2842 app_msgarg(&args
, NULL
);
2843 dup2(fileno(ce
->ce_fp
), 1);
2844 close(fileno(ce
->ce_fp
));
2845 execvp(program
, args
.msgs
);
2846 fprintf(stderr
, "Unable to exec ");
2852 if (pidXwait(child_id
, NULL
)) {
2860 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2865 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2866 if ((fp
= fopen(cachefile
, "w"))) {
2868 FILE *gp
= ce
->ce_fp
;
2870 fseeko(gp
, 0, SEEK_SET
);
2872 while ((cc
= fread(buffer
, sizeof(*buffer
),
2873 sizeof(buffer
), gp
)) > 0)
2874 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2875 advise ("openURL", "fwrite");
2881 admonish(ce
->ce_file
, "error reading");
2882 (void) m_unlink (cachefile
);
2889 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2890 *file
= ce
->ce_file
;
2896 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2897 * has to be base64 decoded.
2900 readDigest (CT ct
, char *cp
)
2902 unsigned char *digest
;
2905 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2906 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2908 if (strlen ((char *) digest
) <= maxlen
) {
2909 memcpy (ct
->c_digest
, digest
, maxlen
);
2914 fprintf (stderr
, "MD5 digest=");
2915 for (i
= 0; i
< maxlen
; ++i
) {
2916 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2918 fprintf (stderr
, "\n");
2924 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2925 (int) strlen ((char *) digest
));
2935 /* Multipart parts might have content before the first subpart and/or
2936 after the last subpart that hasn't been stored anywhere else, so do
2939 get_leftover_mp_content (CT ct
, int before
/* or after */)
2941 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2943 int found_boundary
= 0;
2949 char *content
= NULL
;
2951 if (! m
) return NOTOK
;
2954 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2956 /* Isolate the beginning of this part to the beginning of the
2957 first subpart and save any content between them. */
2958 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2959 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2960 boundary
= concat ("--", m
->mp_start
, NULL
);
2962 struct part
*last_subpart
= NULL
;
2963 struct part
*subpart
;
2965 /* Go to the last subpart to get its end position. */
2966 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2967 last_subpart
= subpart
;
2970 if (last_subpart
== NULL
) return NOTOK
;
2972 /* Isolate the end of the last subpart to the end of this part
2973 and save any content between them. */
2974 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2975 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2976 boundary
= concat ("--", m
->mp_stop
, NULL
);
2979 /* Back up by 1 to pick up the newline. */
2980 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2982 /* Don't look beyond beginning of first subpart (before) or
2983 next part (after). */
2984 if (read
> max
) bufp
[read
-max
] = '\0';
2987 if (! strcmp (bufp
, boundary
)) {
2991 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2997 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2999 char *old_content
= content
;
3000 content
= concat (content
, bufp
, NULL
);
3004 ? concat ("\n", bufp
, NULL
)
3005 : concat (bufp
, NULL
);
3010 if (found_boundary
|| read
> max
) break;
3012 if (read
> max
) break;
3016 /* Skip the newline if that's all there is. */
3020 /* Remove trailing newline, except at EOF. */
3021 if ((before
|| ! feof (ct
->c_fp
)) &&
3022 (cp
= content
+ strlen (content
)) > content
&&
3027 if (strlen (content
) > 1) {
3029 m
->mp_content_before
= content
;
3031 m
->mp_content_after
= content
;
3046 ct_type_str (int type
) {
3048 case CT_APPLICATION
:
3049 return "application";
3065 return "unknown_type";
3071 ct_subtype_str (int type
, int subtype
) {
3073 case CT_APPLICATION
:
3075 case APPLICATION_OCTETS
:
3077 case APPLICATION_POSTSCRIPT
:
3078 return "postscript";
3080 return "unknown_app_subtype";
3084 case MESSAGE_RFC822
:
3086 case MESSAGE_PARTIAL
:
3088 case MESSAGE_EXTERNAL
:
3091 return "unknown_msg_subtype";
3097 case MULTI_ALTERNATE
:
3098 return "alternative";
3101 case MULTI_PARALLEL
:
3106 return "unknown_multipart_subtype";
3117 return "unknown_text_subtype";
3120 return "unknown_type";
3126 ct_str_type (const char *type
) {
3127 struct str2init
*s2i
;
3129 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3130 if (! strcasecmp (type
, s2i
->si_key
)) {
3134 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3143 ct_str_subtype (int type
, const char *subtype
) {
3147 case CT_APPLICATION
:
3148 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3149 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3153 return kv
->kv_value
;
3155 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3156 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3160 return kv
->kv_value
;
3162 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3163 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3167 return kv
->kv_value
;
3169 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3170 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3174 return kv
->kv_value
;
3181 /* Find the content type and InitFunc for the CT. */
3182 const struct str2init
*
3183 get_ct_init (int type
) {
3184 const struct str2init
*sp
;
3186 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3187 if (type
== sp
->si_val
) {
3196 ce_str (int encoding
) {
3201 return "quoted-printable";
3217 /* Find the content type and InitFunc for the content encoding method. */
3218 const struct str2init
*
3219 get_ce_method (const char *method
) {
3220 struct str2init
*sp
;
3222 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3223 if (! strcasecmp (method
, sp
->si_key
)) {
3232 * Parse a series of MIME attributes (or parameters) given a header as
3235 * Arguments include:
3237 * filename - Name of input file (for error messages)
3238 * fieldname - Name of field being processed
3239 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3240 * Updated to point to end of attributes when finished.
3241 * param_head - Pointer to head of parameter list
3242 * param_tail - Pointer to tail of parameter list
3243 * commentp - Pointer to header comment pointer (may be NULL)
3245 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3246 * DONE to indicate a benign error (minor parsing error, but the program
3251 parse_header_attrs (const char *filename
, const char *fieldname
,
3252 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3255 char *cp
= *header_attrp
;
3261 struct sectlist
*next
;
3267 struct sectlist
*sechead
;
3268 struct parmlist
*next
;
3269 } *pp
, *pp2
, *phead
= NULL
;
3271 while (*cp
== ';') {
3272 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3273 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3276 while (isspace ((unsigned char) *cp
))
3280 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3285 if (! suppress_extraneous_trailing_semicolon_warning
) {
3287 "extraneous trailing ';' in message %s's %s: "
3289 filename
, fieldname
);
3291 extraneous_trailing_semicolon
= 1;
3295 /* down case the attribute name */
3296 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3297 *dp
= tolower ((unsigned char) *dp
);
3299 for (up
= dp
; isspace ((unsigned char) *dp
);)
3301 if (dp
== cp
|| *dp
!= '=') {
3303 "invalid parameter in message %s's %s: "
3304 "field\n%*sparameter %s (error detected at offset %d)",
3305 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3310 * To handle RFC 2231, we have to deal with the following extensions:
3312 * name*=encoded-value
3313 * name*<N>=part-N-of-a-parameter-value
3314 * name*<N>*=encoded-part-N-of-a-parameter-value
3317 * If there's a * right before the equal sign, it's encoded.
3318 * If there's a * and one or more digits, then it's section N.
3320 * Remember we can have one or the other, or both. cp points to
3321 * beginning of name, up points past the last character in the
3325 for (vp
= cp
; vp
< up
; vp
++) {
3326 if (*vp
== '*' && vp
< up
- 1) {
3329 } else if (*vp
== '*' && vp
== up
- 1) {
3331 } else if (partial
) {
3332 if (isdigit((unsigned char) *vp
))
3333 index
= *vp
- '0' + index
* 10;
3335 advise (NULL
, "invalid parameter index in message %s's "
3336 "%s: field\n%*s(parameter %s)", filename
,
3337 fieldname
, strlen(invo_name
) + 2, "", cp
);
3346 * Break out the parameter name and value sections and allocate
3350 nameptr
= mh_xmalloc(len
+ 1);
3351 strncpy(nameptr
, cp
, len
);
3352 nameptr
[len
] = '\0';
3354 for (dp
++; isspace ((unsigned char) *dp
);)
3359 * Single quotes delimit the character set and language tag.
3360 * They are required on the first section (or a complete
3365 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3371 charset
= mh_xmalloc(len
+ 1);
3372 strncpy(charset
, dp
, len
);
3373 charset
[len
] = '\0';
3379 advise(NULL
, "missing charset in message %s's %s: "
3380 "field\n%*s(parameter %s)", filename
, fieldname
,
3381 strlen(invo_name
) + 2, "", nameptr
);
3387 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3394 lang
= mh_xmalloc(len
+ 1);
3395 strncpy(lang
, dp
, len
);
3402 advise(NULL
, "missing language tag in message %s's %s: "
3403 "field\n%*s(parameter %s)", filename
, fieldname
,
3404 strlen(invo_name
) + 2, "", nameptr
);
3414 * At this point vp should be pointing at the beginning
3415 * of the encoded value/section. Continue until we reach
3416 * the end or get whitespace. But first, calculate the
3417 * length so we can allocate the correct buffer size.
3420 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3422 if (*(vp
+ 1) == '\0' ||
3423 !isxdigit((unsigned char) *(vp
+ 1)) ||
3424 *(vp
+ 2) == '\0' ||
3425 !isxdigit((unsigned char) *(vp
+ 2))) {
3426 advise(NULL
, "invalid encoded sequence in message "
3427 "%s's %s: field\n%*s(parameter %s)",
3428 filename
, fieldname
, strlen(invo_name
) + 2,
3440 up
= valptr
= mh_xmalloc(len
+ 1);
3442 for (vp
= dp
; istoken(*vp
); vp
++) {
3444 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3455 * A "normal" string. If it's got a leading quote, then we
3456 * strip the quotes out. Otherwise go until we reach the end
3457 * or get whitespace. Note we scan it twice; once to get the
3458 * length, then the second time copies it into the destination
3465 for (cp
= dp
+ 1;;) {
3470 "invalid quoted-string in message %s's %s: "
3471 "field\n%*s(parameter %s)",
3472 filename
, fieldname
, strlen(invo_name
) + 2, "",
3493 for (cp
= dp
; istoken (*cp
); cp
++) {
3498 valptr
= mh_xmalloc(len
+ 1);
3502 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3510 strncpy(valptr
, cp
= dp
, len
);
3518 * If 'partial' is set, we don't allocate a parameter now. We
3519 * put it on the parameter linked list to be reassembled later.
3521 * "phead" points to a list of all parameters we need to reassemble.
3522 * Each parameter has a list of sections. We insert the sections in
3527 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3528 if (strcasecmp(nameptr
, pp
->name
) == 0)
3540 * Insert this into the section linked list
3548 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3549 sp
->next
= pp
->sechead
;
3552 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3553 if (sp2
->index
== sp
->index
) {
3554 advise (NULL
, "duplicate index (%d) in message "
3555 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3556 filename
, fieldname
, strlen(invo_name
) + 2, "",
3561 if (sp2
->index
< sp
->index
&&
3562 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3563 sp
->next
= sp2
->next
;
3570 advise(NULL
, "Internal error: cannot insert partial "
3571 "param in message %s's %s: field\n%*s(parameter %s)",
3572 filename
, fieldname
, strlen(invo_name
) + 2, "",
3580 * Save our charset and lang tags.
3583 if (index
== 0 && encoded
) {
3584 mh_xfree(pp
->charset
);
3585 pp
->charset
= charset
;
3590 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3591 pm
->pm_charset
= charset
;
3595 while (isspace ((unsigned char) *cp
))
3599 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3605 * Now that we're done, reassemble all of the partial parameters.
3608 for (pp
= phead
; pp
!= NULL
; ) {
3612 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3613 if (sp
->index
!= pindex
++) {
3614 advise(NULL
, "missing section %d for parameter in "
3615 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3616 filename
, fieldname
, strlen(invo_name
) + 2, "",
3623 p
= q
= mh_xmalloc(tlen
+ 1);
3624 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3625 memcpy(q
, sp
->value
, sp
->len
);
3635 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3636 pm
->pm_charset
= pp
->charset
;
3637 pm
->pm_lang
= pp
->lang
;
3648 * Return the charset for a particular content type.
3652 content_charset (CT ct
) {
3653 char *ret_charset
= NULL
;
3655 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3657 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3662 * Create a string based on a list of output parameters. Assume that this
3663 * parameter string will be appended to an existing header, so start out
3664 * with the separator (;). Perform RFC 2231 encoding when necessary.
3668 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3670 char *paramout
= NULL
;
3671 char line
[CPERLIN
* 2], *q
;
3672 int curlen
, index
, cont
, encode
, i
;
3673 size_t valoff
, numchars
;
3675 while (params
!= NULL
) {
3681 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3684 if (strlen(params
->pm_name
) > CPERLIN
) {
3685 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3690 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3693 * Loop until we get a parameter that fits within a line. We
3694 * assume new lines start with a tab, so check our overflow based
3704 * At this point we're definitely continuing the line, so
3705 * be sure to include the parameter name and section index.
3708 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3709 params
->pm_name
, index
);
3712 * Both of these functions do a NUL termination
3716 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3717 numchars
, valoff
, index
);
3719 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3729 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3734 * "line" starts with a ;\n\t, so that doesn't count against
3735 * the length. But add 8 since it starts with a tab; that's
3736 * how we end up with 5.
3739 initialwidth
= strlen(line
) + 5;
3742 * At this point the line should be built, so add it to our
3743 * current output buffer.
3746 paramout
= add(line
, paramout
);
3750 * If this won't fit on the line, start a new one. Save room in
3751 * case we need a semicolon on the end
3754 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3766 * At this point, we're either finishing a contined parameter, or
3767 * we're working on a new one.
3771 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3772 params
->pm_name
, index
);
3774 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3779 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3780 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3782 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3783 strlen(params
->pm_value
+ valoff
), valoff
);
3790 paramout
= add(line
, paramout
);
3791 initialwidth
+= strlen(line
);
3793 params
= params
->pm_next
;
3797 *offsetout
= initialwidth
;
3803 * Calculate the size of a parameter.
3807 * pm - The parameter being output
3808 * index - If continuing the parameter, the index of the section
3810 * valueoff - The current offset into the parameter value that we're
3811 * working on (previous sections have consumed valueoff bytes).
3812 * encode - Set if we should perform encoding on this parameter section
3813 * (given that we're consuming bytesfit bytes).
3814 * cont - Set if the remaining data in value will not fit on a single
3815 * line and will need to be continued.
3816 * bytesfit - The number of bytes that we can consume from the parameter
3817 * value and still fit on a completely new line. The
3818 * calculation assumes the new line starts with a tab,
3819 * includes the parameter name and any encoding, and fits
3820 * within CPERLIN bytes. Will always be at least 1.
3824 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3827 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3828 size_t len
= 0, fit
= 0;
3829 int fitlimit
= 0, eightbit
, maxfit
;
3834 * Add up the length. First, start with the parameter name.
3837 len
= strlen(pm
->pm_name
);
3840 * Scan the parameter value and see if we need to do encoding for this
3844 eightbit
= contains8bit(start
, NULL
);
3847 * Determine if we need to encode this section. Encoding is necessary if:
3849 * - There are any 8-bit characters at all and we're on the first
3851 * - There are 8-bit characters within N bytes of our section start.
3852 * N is calculated based on the number of bytes it would take to
3853 * reach CPERLIN. Specifically:
3854 * 8 (starting tab) +
3855 * strlen(param name) +
3856 * 4 ('* for section marker, '=', opening/closing '"')
3858 * is the number of bytes used by everything that isn't part of the
3859 * value. So that gets subtracted from CPERLIN.
3862 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3863 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3864 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3868 len
++; /* Add in equal sign */
3872 * We're using maxfit as a marker for how many characters we can
3873 * fit into the line. Bump it by two because we're not using quotes
3880 * If we don't have a charset or language tag in this parameter,
3884 if (! pm
->pm_charset
) {
3885 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3886 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3887 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3888 "local character set is US-ASCII", pm
->pm_name
);
3891 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3893 len
++; /* For the encoding marker */
3896 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3901 * We know we definitely need to include an index. maxfit already
3902 * includes the section marker.
3904 len
+= strlen(indexchar
);
3906 for (p
= start
; *p
!= '\0'; p
++) {
3907 if (isparamencode(*p
)) {
3915 * Just so there's no confusion: maxfit is counting OUTPUT
3916 * characters (post-encoding). fit is counting INPUT characters.
3918 if (! fitlimit
&& maxfit
>= 0)
3920 else if (! fitlimit
)
3925 * Calculate the string length, but add room for quoting \
3926 * and " if necessary. Also account for quotes at beginning
3929 for (p
= start
; *p
!= '\0'; p
++) {
3940 if (! fitlimit
&& maxfit
>= 0)
3942 else if (! fitlimit
)
3959 * Output an encoded parameter string.
3963 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3964 size_t valueoff
, int index
)
3966 size_t outlen
= 0, n
;
3967 char *endptr
= output
+ len
, *p
;
3970 * First, output the marker for an encoded string.
3978 * If the index is 0, output the character set and language tag.
3979 * If theses were NULL, they should have already been filled in
3984 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3988 if (output
> endptr
) {
3989 advise(NULL
, "Internal error: parameter buffer overflow");
3995 * Copy over the value, encoding if necessary
3998 p
= pm
->pm_value
+ valueoff
;
3999 while (valuelen
-- > 0) {
4000 if (isparamencode(*p
)) {
4001 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4008 if (output
> endptr
) {
4009 advise(NULL
, "Internal error: parameter buffer overflow");
4020 * Output a "normal" parameter, without encoding. Be sure to escape
4021 * quotes and backslashes if necessary.
4025 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4029 char *endptr
= output
+ len
, *p
;
4035 p
= pm
->pm_value
+ valueoff
;
4037 while (valuelen
-- > 0) {
4047 if (output
> endptr
) {
4048 advise(NULL
, "Internal error: parameter buffer overflow");
4053 if (output
- 2 > endptr
) {
4054 advise(NULL
, "Internal error: parameter buffer overflow");
4065 * Add a parameter to the parameter linked list
4069 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4074 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4075 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4078 (*last
)->pm_next
= pm
;
4089 * Either replace a current parameter with a new value, or add the parameter
4090 * to the parameter linked list.
4094 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4098 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4099 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4101 * If nocopy is set, it's assumed that we own both name
4102 * and value. We don't need name, so we discard it now.
4107 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4112 return add_param(first
, last
, name
, value
, nocopy
);
4116 * Retrieve a parameter value from a parameter linked list. If the parameter
4117 * value needs converted to the local character set, do that now.
4121 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4123 while (first
!= NULL
) {
4124 if (strcasecmp(name
, first
->pm_name
) == 0) {
4126 return first
->pm_value
;
4127 return getcpy(get_param_value(first
, replace
));
4129 first
= first
->pm_next
;
4136 * Return a parameter value, converting to the local character set if
4140 char *get_param_value(PM pm
, char replace
)
4142 static char buffer
[4096]; /* I hope no parameters are larger */
4143 size_t bufsize
= sizeof(buffer
);
4148 ICONV_CONST
char *p
;
4149 #else /* HAVE_ICONV */
4151 #endif /* HAVE_ICONV */
4156 * If we don't have a character set indicated, it's assumed to be
4157 * US-ASCII. If it matches our character set, we don't need to convert
4161 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4162 strlen(pm
->pm_charset
))) {
4163 return pm
->pm_value
;
4167 * In this case, we need to convert. If we have iconv support, use
4168 * that. Otherwise, go through and simply replace every non-ASCII
4169 * character with the substitution character.
4174 bufsize
= sizeof(buffer
);
4175 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4177 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4178 if (cd
== (iconv_t
) -1) {
4182 inbytes
= strlen(pm
->pm_value
);
4186 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4187 if (errno
!= EILSEQ
) {
4192 * Reset shift state, substitute our character,
4193 * try to restart conversion.
4196 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4209 for (++p
, --inbytes
;
4210 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4229 #endif /* HAVE_ICONV */
4232 * Take everything non-ASCII and substituite the replacement character
4236 bufsize
= sizeof(buffer
);
4237 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4238 /* FIXME: !iscntrl should perhaps be isprint as that allows all
4239 * classes bar cntrl, whereas the cntrl class can include those
4240 * in space and blank.
4241 * http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html */
4242 if (isascii((unsigned char) *p
) && !iscntrl((unsigned char) *p
))