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
39 int skip_mp_cte_check
;
40 int suppress_bogus_mp_content_warning
;
42 int suppress_extraneous_trailing_semicolon_warning
;
45 * By default, suppress warning about multiple MIME-Version header fields.
47 int suppress_multiple_mime_version_warning
= 1;
49 /* list of preferred type/subtype pairs, for -prefer */
50 char *preferred_types
[NPREFS
],
51 *preferred_subtypes
[NPREFS
];
56 * Structures for TEXT messages
58 struct k2v SubText
[] = {
59 { "plain", TEXT_PLAIN
},
60 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC-1341 */
61 { "enriched", TEXT_ENRICHED
}, /* defined in RFC-1896 */
62 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
65 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
68 * Structures for MULTIPART messages
70 struct k2v SubMultiPart
[] = {
71 { "mixed", MULTI_MIXED
},
72 { "alternative", MULTI_ALTERNATE
},
73 { "digest", MULTI_DIGEST
},
74 { "parallel", MULTI_PARALLEL
},
75 { "related", MULTI_RELATED
},
76 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
80 * Structures for MESSAGE messages
82 struct k2v SubMessage
[] = {
83 { "rfc822", MESSAGE_RFC822
},
84 { "partial", MESSAGE_PARTIAL
},
85 { "external-body", MESSAGE_EXTERNAL
},
86 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
90 * Structure for APPLICATION messages
92 struct k2v SubApplication
[] = {
93 { "octet-stream", APPLICATION_OCTETS
},
94 { "postscript", APPLICATION_POSTSCRIPT
},
95 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
99 * Mapping of names of CTE types in mhbuild directives
101 static struct k2v EncodingType
[] = {
104 { "q-p", CE_QUOTED
},
105 { "quoted-printable", CE_QUOTED
},
106 { "b64", CE_BASE64
},
107 { "base64", CE_BASE64
},
113 int find_cache (CT
, int, int *, char *, char *, int);
117 int type_ok (CT
, int);
118 void content_error (char *, CT
, char *, ...);
121 void free_encoding (CT
, int);
126 static CT
get_content (FILE *, char *, int);
127 static int get_comment (const char *, const char *, char **, char **);
129 static int InitGeneric (CT
);
130 static int InitText (CT
);
131 static int InitMultiPart (CT
);
132 static void reverse_parts (CT
);
133 static void prefer_parts(CT ct
);
134 static int InitMessage (CT
);
135 static int InitApplication (CT
);
136 static int init_encoding (CT
, OpenCEFunc
);
137 static unsigned long size_encoding (CT
);
138 static int InitBase64 (CT
);
139 static int openBase64 (CT
, char **);
140 static int InitQuoted (CT
);
141 static int openQuoted (CT
, char **);
142 static int Init7Bit (CT
);
143 static int openExternal (CT
, CT
, CE
, char **, int *);
144 static int InitFile (CT
);
145 static int openFile (CT
, char **);
146 static int InitFTP (CT
);
147 static int openFTP (CT
, char **);
148 static int InitMail (CT
);
149 static int openMail (CT
, char **);
150 static int readDigest (CT
, char *);
151 static int get_leftover_mp_content (CT
, int);
152 static int InitURL (CT
);
153 static int openURL (CT
, char **);
154 static int parse_header_attrs (const char *, const char *, char **, PM
*,
156 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
157 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
158 static int get_dispo (char *, CT
, int);
160 struct str2init str2cts
[] = {
161 { "application", CT_APPLICATION
, InitApplication
},
162 { "audio", CT_AUDIO
, InitGeneric
},
163 { "image", CT_IMAGE
, InitGeneric
},
164 { "message", CT_MESSAGE
, InitMessage
},
165 { "multipart", CT_MULTIPART
, InitMultiPart
},
166 { "text", CT_TEXT
, InitText
},
167 { "video", CT_VIDEO
, InitGeneric
},
168 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
169 { NULL
, CT_UNKNOWN
, NULL
},
172 struct str2init str2ces
[] = {
173 { "base64", CE_BASE64
, InitBase64
},
174 { "quoted-printable", CE_QUOTED
, InitQuoted
},
175 { "8bit", CE_8BIT
, Init7Bit
},
176 { "7bit", CE_7BIT
, Init7Bit
},
177 { "binary", CE_BINARY
, Init7Bit
},
178 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
179 { NULL
, CE_UNKNOWN
, NULL
},
183 * NOTE WELL: si_key MUST NOT have value of NOTOK
185 * si_key is 1 if access method is anonymous.
187 struct str2init str2methods
[] = {
188 { "afs", 1, InitFile
},
189 { "anon-ftp", 1, InitFTP
},
190 { "ftp", 0, InitFTP
},
191 { "local-file", 0, InitFile
},
192 { "mail-server", 0, InitMail
},
193 { "url", 0, InitURL
},
199 * Main entry point for parsing a MIME message or file.
200 * It returns the Content structure for the top level
201 * entity in the file.
205 parse_mime (char *file
)
213 bogus_mp_content
= 0;
216 * Check if file is actually standard input
218 if ((is_stdin
= !(strcmp (file
, "-")))) {
219 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
221 advise("mhparse", "unable to create temporary file in %s",
225 file
= mh_xstrdup(tfile
);
227 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
228 if (fwrite(buffer
, 1, n
, fp
) != n
) {
229 (void) m_unlink (file
);
230 advise (file
, "error copying to temporary file");
236 if (ferror (stdin
)) {
237 (void) m_unlink (file
);
238 advise ("stdin", "error reading");
242 (void) m_unlink (file
);
243 advise (file
, "error writing");
246 fseek (fp
, 0L, SEEK_SET
);
247 } else if ((fp
= fopen (file
, "r")) == NULL
) {
248 advise (file
, "unable to read");
252 if (!(ct
= get_content (fp
, file
, 1))) {
254 (void) m_unlink (file
);
255 advise (NULL
, "unable to decode %s", file
);
260 ct
->c_unlink
= 1; /* temp file to remove */
264 if (ct
->c_end
== 0L) {
265 fseek (fp
, 0L, SEEK_END
);
266 ct
->c_end
= ftell (fp
);
269 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
281 * Main routine for reading/parsing the headers
282 * of a message content.
284 * toplevel = 1 # we are at the top level of the message
285 * toplevel = 0 # we are inside message type or multipart type
286 * # other than multipart/digest
287 * toplevel = -1 # we are inside multipart/digest
288 * NB: on failure we will fclose(in)!
292 get_content (FILE *in
, char *file
, int toplevel
)
295 char buf
[BUFSIZ
], name
[NAMESZ
];
299 m_getfld_state_t gstate
= 0;
301 /* allocate the content structure */
304 ct
->c_file
= add (file
, NULL
);
305 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
308 * Parse the header fields for this
309 * content into a linked list.
311 m_getfld_track_filepos (&gstate
, in
);
312 for (compnum
= 1;;) {
313 int bufsz
= sizeof buf
;
314 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
319 /* get copies of the buffers */
320 np
= mh_xstrdup(name
);
321 vp
= mh_xstrdup(buf
);
323 /* if necessary, get rest of field */
324 while (state
== FLDPLUS
) {
326 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
327 vp
= add (buf
, vp
); /* add to previous value */
330 /* Now add the header data to the list */
331 add_header (ct
, np
, vp
);
333 /* continue, to see if this isn't the last header field */
334 ct
->c_begin
= ftell (in
) + 1;
338 if (name
[0] == ':') {
339 /* Special case: no blank line between header and body. The
340 file position indicator is on the newline at the end of the
341 line, but it needs to be one prior to the beginning of the
342 line. So subtract the length of the line, bufsz, plus 1. */
343 ct
->c_begin
= ftell (in
) - (bufsz
+ 1);
345 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
350 ct
->c_begin
= ftell (in
);
355 adios (NULL
, "message format error in component #%d", compnum
);
358 adios (NULL
, "getfld() returned %d", state
);
361 /* break out of the loop */
364 m_getfld_state_destroy (&gstate
);
367 * Read the content headers. We will parse the
368 * MIME related header fields into their various
369 * structures and set internal flags related to
370 * content type/subtype, etc.
373 hp
= ct
->c_first_hf
; /* start at first header field */
375 /* Get MIME-Version field */
376 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
381 vrsn
= add (hp
->value
, NULL
);
383 /* Now, cleanup this field */
386 while (isspace ((unsigned char) *cp
))
388 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
390 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
391 if (!isspace ((unsigned char) *dp
))
395 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
398 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
401 for (dp
= cp
; istoken (*dp
); dp
++)
405 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
408 admonish (NULL
, "message %s has unknown value for %s: field (%s)",
409 ct
->c_file
, VRSN_FIELD
, cp
);
414 if (! suppress_multiple_mime_version_warning
)
415 advise (NULL
, "message %s has multiple %s: fields",
416 ct
->c_file
, VRSN_FIELD
);
420 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
421 /* Get Content-Type field */
422 struct str2init
*s2i
;
423 CI ci
= &ct
->c_ctinfo
;
425 /* Check if we've already seen a Content-Type header */
427 advise (NULL
, "message %s has multiple %s: fields",
428 ct
->c_file
, TYPE_FIELD
);
432 /* Parse the Content-Type field */
433 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
437 * Set the Init function and the internal
438 * flag for this content type.
440 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
441 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
443 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
445 ct
->c_type
= s2i
->si_val
;
446 ct
->c_ctinitfnx
= s2i
->si_init
;
448 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
449 /* Get Content-Transfer-Encoding field */
451 struct str2init
*s2i
;
454 * Check if we've already seen the
455 * Content-Transfer-Encoding field
458 advise (NULL
, "message %s has multiple %s: fields",
459 ct
->c_file
, ENCODING_FIELD
);
463 /* get copy of this field */
464 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
466 while (isspace ((unsigned char) *cp
))
468 for (dp
= cp
; istoken (*dp
); dp
++)
474 * Find the internal flag and Init function
475 * for this transfer encoding.
477 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
478 if (!strcasecmp (cp
, s2i
->si_key
))
480 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
483 ct
->c_encoding
= s2i
->si_val
;
485 /* Call the Init function for this encoding */
486 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
489 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
490 /* Get Content-MD5 field */
496 if (ct
->c_digested
) {
497 advise (NULL
, "message %s has multiple %s: fields",
498 ct
->c_file
, MD5_FIELD
);
502 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
504 while (isspace ((unsigned char) *cp
))
506 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
508 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
509 if (!isspace ((unsigned char) *dp
))
513 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
516 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
521 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
529 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
530 /* Get Content-ID field */
531 ct
->c_id
= add (hp
->value
, ct
->c_id
);
533 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
534 /* Get Content-Description field */
535 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
537 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
538 /* Get Content-Disposition field */
539 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
544 hp
= hp
->next
; /* next header field */
548 * Check if we saw a Content-Type field.
549 * If not, then assign a default value for
550 * it, and the Init function.
554 * If we are inside a multipart/digest message,
555 * so default type is message/rfc822
558 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
560 ct
->c_type
= CT_MESSAGE
;
561 ct
->c_ctinitfnx
= InitMessage
;
564 * Else default type is text/plain
566 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
568 ct
->c_type
= CT_TEXT
;
569 ct
->c_ctinitfnx
= InitText
;
573 /* Use default Transfer-Encoding, if necessary */
575 ct
->c_encoding
= CE_7BIT
;
588 * small routine to add header field to list
592 add_header (CT ct
, char *name
, char *value
)
596 /* allocate header field structure */
599 /* link data into header structure */
604 /* link header structure into the list */
605 if (ct
->c_first_hf
== NULL
) {
606 ct
->c_first_hf
= hp
; /* this is the first */
609 ct
->c_last_hf
->next
= hp
; /* add it to the end */
618 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
619 * directives. Fills in the information of the CTinfo structure.
622 get_ctinfo (char *cp
, CT ct
, int magic
)
631 /* store copy of Content-Type line */
632 cp
= ct
->c_ctline
= add (cp
, NULL
);
634 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
637 /* change newlines to spaces */
638 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
641 /* trim trailing spaces */
642 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
643 if (!isspace ((unsigned char) *dp
))
648 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
650 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
651 &ci
->ci_comment
) == NOTOK
)
654 for (dp
= cp
; istoken (*dp
); dp
++)
657 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
661 advise (NULL
, "invalid %s: field in message %s (empty type)",
662 TYPE_FIELD
, ct
->c_file
);
665 ToLower(ci
->ci_type
);
667 while (isspace ((unsigned char) *cp
))
670 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
671 &ci
->ci_comment
) == NOTOK
)
676 ci
->ci_subtype
= mh_xstrdup("");
681 while (isspace ((unsigned char) *cp
))
684 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
685 &ci
->ci_comment
) == NOTOK
)
688 for (dp
= cp
; istoken (*dp
); dp
++)
691 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
694 if (!*ci
->ci_subtype
) {
696 "invalid %s: field in message %s (empty subtype for \"%s\")",
697 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
700 ToLower(ci
->ci_subtype
);
703 while (isspace ((unsigned char) *cp
))
706 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
707 &ci
->ci_comment
) == NOTOK
)
710 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
711 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
712 &ci
->ci_comment
)) != OK
) {
713 return status
== NOTOK
? NOTOK
: OK
;
717 * Get any <Content-Id> given in buffer
719 if (magic
&& *cp
== '<') {
722 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
723 advise (NULL
, "invalid ID in message %s", ct
->c_file
);
729 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
735 while (isspace ((unsigned char) *cp
))
740 * Get any [Content-Description] given in buffer.
742 if (magic
&& *cp
== '[') {
744 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
748 advise (NULL
, "invalid description in message %s", ct
->c_file
);
756 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
762 while (isspace ((unsigned char) *cp
))
767 * Get any {Content-Disposition} given in buffer.
769 if (magic
&& *cp
== '{') {
771 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
775 advise (NULL
, "invalid disposition in message %s", ct
->c_file
);
783 if (get_dispo(cp
, ct
, 1) != OK
)
789 while (isspace ((unsigned char) *cp
))
794 * Get any extension directives (right now just the content transfer
795 * encoding, but maybe others) that we care about.
798 if (magic
&& *cp
== '*') {
800 * See if it's a CTE we match on
805 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
809 advise (NULL
, "invalid null transfer encoding specification");
816 ct
->c_reqencoding
= CE_UNKNOWN
;
818 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
819 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
820 ct
->c_reqencoding
= kv
->kv_value
;
825 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
826 advise (NULL
, "invalid CTE specification: \"%s\"", dp
);
830 while (isspace ((unsigned char) *cp
))
835 * Check if anything is left over
839 ci
->ci_magic
= mh_xstrdup(cp
);
841 /* If there is a Content-Disposition header and it doesn't
842 have a *filename=, extract it from the magic contents.
843 The r1bindex call skips any leading directory
845 if (ct
->c_dispo_type
&&
846 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
847 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
848 r1bindex(ci
->ci_magic
, '/'), 0);
853 "extraneous information in message %s's %s: field\n%*s(%s)",
854 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
862 * Parse out a Content-Disposition header. A lot of this is cribbed from
866 get_dispo (char *cp
, CT ct
, int buildflag
)
868 char *dp
, *dispoheader
;
873 * Save the whole copy of the Content-Disposition header, unless we're
874 * processing a mhbuild directive. A NULL c_dispo will be a flag to
875 * mhbuild that the disposition header needs to be generated at that
879 dispoheader
= cp
= add(cp
, NULL
);
881 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
884 /* change newlines to spaces */
885 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
888 /* trim trailing spaces */
889 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
890 if (!isspace ((unsigned char) *dp
))
895 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
897 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
903 for (dp
= cp
; istoken (*dp
); dp
++)
906 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
909 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
912 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
913 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
915 if (status
== NOTOK
) {
921 "extraneous information in message %s's %s: field\n%*s(%s)",
922 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
928 ct
->c_dispo
= dispoheader
;
935 get_comment (const char *filename
, const char *fieldname
, char **ap
,
940 char c
, buffer
[BUFSIZ
], *dp
;
950 advise (NULL
, "invalid comment in message %s's %s: field",
951 filename
, fieldname
);
956 if ((c
= *cp
++) == '\0')
979 if ((dp
= *commentp
)) {
980 *commentp
= concat (dp
, " ", buffer
, NULL
);
983 *commentp
= mh_xstrdup(buffer
);
987 while (isspace ((unsigned char) *cp
))
998 * Handles content types audio, image, and video.
999 * There's not much to do right here.
1007 return OK
; /* not much to do here */
1018 char buffer
[BUFSIZ
];
1023 CI ci
= &ct
->c_ctinfo
;
1025 /* check for missing subtype */
1026 if (!*ci
->ci_subtype
)
1027 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1030 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1032 /* allocate text character set structure */
1034 ct
->c_ctparams
= (void *) t
;
1036 /* scan for charset parameter */
1037 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1038 if (!strcasecmp (pm
->pm_name
, "charset"))
1041 /* check if content specified a character set */
1043 chset
= pm
->pm_value
;
1044 t
->tx_charset
= CHARSET_SPECIFIED
;
1046 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1050 * If we can not handle character set natively,
1051 * then check profile for string to modify the
1052 * terminal or display method.
1054 * termproc is for mhshow, though mhlist -debug prints it, too.
1056 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1057 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1058 if ((cp
= context_find (buffer
)))
1059 ct
->c_termproc
= mh_xstrdup(cp
);
1071 InitMultiPart (CT ct
)
1081 struct multipart
*m
;
1082 struct part
*part
, **next
;
1083 CI ci
= &ct
->c_ctinfo
;
1088 * The encoding for multipart messages must be either
1089 * 7bit, 8bit, or binary (per RFC2045).
1091 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1092 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1093 /* Copy the Content-Transfer-Encoding header field body so we can
1094 remove any trailing whitespace and leading blanks from it. */
1095 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1097 bp
= cte
+ strlen (cte
) - 1;
1098 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1099 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1102 "\"%s/%s\" type in message %s must be encoded in\n"
1103 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1104 "mhfixmsg -fixcte can fix it, or\n"
1105 "manually edit the file and change the \"%s\"\n"
1106 "Content-Transfer-Encoding to one of those. For now",
1107 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1114 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1117 * Check for "boundary" parameter, which is
1118 * required for multipart messages.
1121 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1122 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1128 /* complain if boundary parameter is missing */
1131 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1132 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1136 /* allocate primary structure for multipart info */
1138 ct
->c_ctparams
= (void *) m
;
1140 /* check if boundary parameter contains only whitespace characters */
1141 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1144 advise (NULL
, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1145 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1149 /* remove trailing whitespace from boundary parameter */
1150 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1151 if (!isspace ((unsigned char) *dp
))
1155 /* record boundary separators */
1156 m
->mp_start
= concat (bp
, "\n", NULL
);
1157 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1159 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1160 advise (ct
->c_file
, "unable to open for reading");
1164 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1166 next
= &m
->mp_parts
;
1170 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1175 if (bufp
[0] != '-' || bufp
[1] != '-')
1178 if (strcmp (bufp
+ 2, m
->mp_start
))
1183 next
= &part
->mp_next
;
1185 if (!(p
= get_content (fp
, ct
->c_file
,
1186 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1194 fseek (fp
, pos
, SEEK_SET
);
1197 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1201 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1202 if (p
->c_end
< p
->c_begin
)
1203 p
->c_begin
= p
->c_end
;
1208 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1213 if (! suppress_bogus_mp_content_warning
) {
1214 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1216 bogus_mp_content
= 1;
1218 if (!inout
&& part
) {
1220 p
->c_end
= ct
->c_end
;
1222 if (p
->c_begin
>= p
->c_end
) {
1223 for (next
= &m
->mp_parts
; *next
!= part
;
1224 next
= &((*next
)->mp_next
))
1233 /* reverse the order of the parts for multipart/alternative */
1234 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1240 * label all subparts with part number, and
1241 * then initialize the content of the subpart.
1246 char partnam
[BUFSIZ
];
1249 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1250 pp
= partnam
+ strlen (partnam
);
1255 for (part
= m
->mp_parts
, partnum
= 1; part
;
1256 part
= part
->mp_next
, partnum
++) {
1259 sprintf (pp
, "%d", partnum
);
1260 p
->c_partno
= mh_xstrdup(partnam
);
1262 /* initialize the content of the subparts */
1263 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1272 get_leftover_mp_content (ct
, 1);
1273 get_leftover_mp_content (ct
, 0);
1283 * reverse the order of the parts of a multipart/alternative,
1284 * presumably to put the "most favored" alternative first, for
1285 * ease of choosing/displaying it later on. from a mail message on
1286 * nmh-workers, from kenh:
1287 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1288 * see code in mhn that did the same thing... Acccording to the RCS
1289 * logs, that code was around from the initial checkin of mhn.c by
1290 * John Romine in 1992, which is as far back as we have."
1293 reverse_parts (CT ct
)
1295 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1299 /* Reverse the order of its parts by walking the mp_parts list
1300 and pushing each node to the front. */
1301 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1302 next
= part
->mp_next
;
1303 part
->mp_next
= m
->mp_parts
;
1309 move_preferred_part (CT ct
, char *type
, char *subtype
)
1311 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1312 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1316 /* move the matching part(s) to the head of the list: walk the
1317 * list of parts, move matching parts to a new list (maintaining
1318 * their order), and finally, concatenate the old list onto the
1325 head
->mp_next
= m
->mp_parts
;
1326 nhead
->mp_next
= NULL
;
1330 part
= head
->mp_next
;
1331 while (part
!= NULL
) {
1332 ci
= &part
->mp_part
->c_ctinfo
;
1333 if (!strcasecmp(ci
->ci_type
, type
) &&
1334 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1335 prev
->mp_next
= part
->mp_next
;
1336 part
->mp_next
= NULL
;
1337 ntail
->mp_next
= part
;
1339 part
= prev
->mp_next
;
1342 part
= prev
->mp_next
;
1345 ntail
->mp_next
= head
->mp_next
;
1346 m
->mp_parts
= nhead
->mp_next
;
1351 * move parts that match the user's preferences (-prefer) to the head
1352 * of the line. process preferences in reverse so first one given
1353 * ends up first in line
1359 for (i
= npreferred
-1; i
>= 0; i
--)
1360 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1365 /* parse_mime() arranges alternates in reverse (priority) order. This
1366 function can be used to reverse them back. This will put, for
1367 example, a text/plain part before a text/html part in a
1368 multipart/alternative part, for example, where it belongs. */
1370 reverse_alternative_parts (CT ct
) {
1371 if (ct
->c_type
== CT_MULTIPART
) {
1372 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1375 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1379 /* And call recursively on each part of a multipart. */
1380 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1381 reverse_alternative_parts (part
->mp_part
);
1394 CI ci
= &ct
->c_ctinfo
;
1396 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1398 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1399 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1403 /* check for missing subtype */
1404 if (!*ci
->ci_subtype
)
1405 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1408 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1410 switch (ct
->c_subtype
) {
1411 case MESSAGE_RFC822
:
1414 case MESSAGE_PARTIAL
:
1420 ct
->c_ctparams
= (void *) p
;
1422 /* scan for parameters "id", "number", and "total" */
1423 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1424 if (!strcasecmp (pm
->pm_name
, "id")) {
1425 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1428 if (!strcasecmp (pm
->pm_name
, "number")) {
1429 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1430 || p
->pm_partno
< 1) {
1433 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1434 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1435 ct
->c_file
, TYPE_FIELD
);
1440 if (!strcasecmp (pm
->pm_name
, "total")) {
1441 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1450 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1452 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1453 ci
->ci_type
, ci
->ci_subtype
,
1454 ct
->c_file
, TYPE_FIELD
);
1460 case MESSAGE_EXTERNAL
:
1468 ct
->c_ctparams
= (void *) e
;
1471 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1472 advise (ct
->c_file
, "unable to open for reading");
1476 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1478 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1486 p
->c_ceopenfnx
= NULL
;
1487 if ((exresult
= params_external (ct
, 0)) != NOTOK
1488 && p
->c_ceopenfnx
== openMail
) {
1492 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1494 content_error (NULL
, ct
,
1495 "empty body for access-type=mail-server");
1499 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1500 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1502 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1504 adios ("failed", "fread");
1507 adios (NULL
, "unexpected EOF from fread");
1510 bp
+= cc
, size
-= cc
;
1517 p
->c_end
= p
->c_begin
;
1522 if (exresult
== NOTOK
)
1524 if (e
->eb_flags
== NOTOK
)
1527 switch (p
->c_type
) {
1532 if (p
->c_subtype
!= MESSAGE_RFC822
)
1536 e
->eb_partno
= ct
->c_partno
;
1538 (*p
->c_ctinitfnx
) (p
);
1553 params_external (CT ct
, int composing
)
1556 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1557 CI ci
= &ct
->c_ctinfo
;
1559 ct
->c_ceopenfnx
= NULL
;
1560 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1561 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1562 struct str2init
*s2i
;
1563 CT p
= e
->eb_content
;
1565 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1566 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1569 e
->eb_access
= pm
->pm_value
;
1570 e
->eb_flags
= NOTOK
;
1571 p
->c_encoding
= CE_EXTERNAL
;
1574 e
->eb_access
= s2i
->si_key
;
1575 e
->eb_flags
= s2i
->si_val
;
1576 p
->c_encoding
= CE_EXTERNAL
;
1578 /* Call the Init function for this external type */
1579 if ((*s2i
->si_init
)(p
) == NOTOK
)
1583 if (!strcasecmp (pm
->pm_name
, "name")) {
1584 e
->eb_name
= pm
->pm_value
;
1587 if (!strcasecmp (pm
->pm_name
, "permission")) {
1588 e
->eb_permission
= pm
->pm_value
;
1591 if (!strcasecmp (pm
->pm_name
, "site")) {
1592 e
->eb_site
= pm
->pm_value
;
1595 if (!strcasecmp (pm
->pm_name
, "directory")) {
1596 e
->eb_dir
= pm
->pm_value
;
1599 if (!strcasecmp (pm
->pm_name
, "mode")) {
1600 e
->eb_mode
= pm
->pm_value
;
1603 if (!strcasecmp (pm
->pm_name
, "size")) {
1604 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1607 if (!strcasecmp (pm
->pm_name
, "server")) {
1608 e
->eb_server
= pm
->pm_value
;
1611 if (!strcasecmp (pm
->pm_name
, "subject")) {
1612 e
->eb_subject
= pm
->pm_value
;
1615 if (!strcasecmp (pm
->pm_name
, "url")) {
1617 * According to RFC 2017, we have to remove all whitespace from
1621 char *u
, *p
= pm
->pm_value
;
1622 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1624 for (; *p
!= '\0'; p
++) {
1625 if (! isspace((unsigned char) *p
))
1632 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1633 e
->eb_body
= getcpy (pm
->pm_value
);
1638 if (!e
->eb_access
) {
1640 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1641 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1654 InitApplication (CT ct
)
1656 CI ci
= &ct
->c_ctinfo
;
1659 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1666 * TRANSFER ENCODINGS
1670 init_encoding (CT ct
, OpenCEFunc openfnx
)
1672 ct
->c_ceopenfnx
= openfnx
;
1673 ct
->c_ceclosefnx
= close_encoding
;
1674 ct
->c_cesizefnx
= size_encoding
;
1681 close_encoding (CT ct
)
1683 CE ce
= &ct
->c_cefile
;
1692 static unsigned long
1693 size_encoding (CT ct
)
1698 CE ce
= &ct
->c_cefile
;
1701 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1702 return (long) st
.st_size
;
1705 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1706 return (long) st
.st_size
;
1710 if (ct
->c_encoding
== CE_EXTERNAL
)
1711 return (ct
->c_end
- ct
->c_begin
);
1714 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1715 return (ct
->c_end
- ct
->c_begin
);
1717 if (fstat (fd
, &st
) != NOTOK
)
1718 size
= (long) st
.st_size
;
1722 (*ct
->c_ceclosefnx
) (ct
);
1734 return init_encoding (ct
, openBase64
);
1739 openBase64 (CT ct
, char **file
)
1742 int fd
, own_ct_fp
= 0;
1743 char *cp
, *buffer
= NULL
;
1744 /* sbeck -- handle suffixes */
1746 CE ce
= &ct
->c_cefile
;
1747 unsigned char *decoded
;
1749 unsigned char digest
[16];
1752 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1757 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1758 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1764 if (*file
== NULL
) {
1767 ce
->ce_file
= mh_xstrdup(*file
);
1771 /* sbeck@cise.ufl.edu -- handle suffixes */
1773 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1774 if (ce
->ce_unlink
) {
1775 /* Create temporary file with filename extension. */
1776 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1777 adios(NULL
, "unable to create temporary file in %s",
1781 ce
->ce_file
= add (cp
, ce
->ce_file
);
1783 } else if (*file
== NULL
) {
1785 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1786 adios(NULL
, "unable to create temporary file in %s",
1789 ce
->ce_file
= mh_xstrdup(tempfile
);
1792 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1793 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1797 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1798 adios (NULL
, "internal error(1)");
1800 buffer
= mh_xmalloc (len
+ 1);
1803 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1804 content_error (ct
->c_file
, ct
, "unable to open for reading");
1810 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1813 switch (cc
= read (fd
, cp
, len
)) {
1815 content_error (ct
->c_file
, ct
, "error reading from");
1819 content_error (NULL
, ct
, "premature eof");
1830 /* decodeBase64() requires null-terminated input. */
1833 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1834 ct
->c_digested
? digest
: NULL
) == OK
) {
1836 unsigned char *decoded_p
= decoded
;
1837 for (i
= 0; i
< decoded_len
; ++i
) {
1838 putc (*decoded_p
++, ce
->ce_fp
);
1841 if (ferror (ce
->ce_fp
)) {
1842 content_error (ce
->ce_file
, ct
, "error writing to");
1846 if (ct
->c_digested
) {
1847 if (memcmp(digest
, ct
->c_digest
,
1848 sizeof(digest
) / sizeof(digest
[0]))) {
1849 content_error (NULL
, ct
,
1850 "content integrity suspect (digest mismatch) -- continuing");
1853 fprintf (stderr
, "content integrity confirmed\n");
1861 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1863 if (fflush (ce
->ce_fp
)) {
1864 content_error (ce
->ce_file
, ct
, "error writing to");
1868 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1871 *file
= ce
->ce_file
;
1877 return fileno (ce
->ce_fp
);
1884 free_encoding (ct
, 0);
1894 static char hex2nib
[0x80] = {
1895 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1896 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1897 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1898 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1899 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1900 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1901 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1902 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1903 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1909 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1910 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1917 return init_encoding (ct
, openQuoted
);
1922 openQuoted (CT ct
, char **file
)
1924 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1930 CE ce
= &ct
->c_cefile
;
1931 /* sbeck -- handle suffixes */
1936 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1941 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1942 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1948 if (*file
== NULL
) {
1951 ce
->ce_file
= mh_xstrdup(*file
);
1955 /* sbeck@cise.ufl.edu -- handle suffixes */
1957 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1958 if (ce
->ce_unlink
) {
1959 /* Create temporary file with filename extension. */
1960 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1961 adios(NULL
, "unable to create temporary file in %s",
1965 ce
->ce_file
= add (cp
, ce
->ce_file
);
1967 } else if (*file
== NULL
) {
1969 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1970 adios(NULL
, "unable to create temporary file in %s",
1973 ce
->ce_file
= mh_xstrdup(tempfile
);
1976 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1977 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1981 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1982 adios (NULL
, "internal error(2)");
1985 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1986 content_error (ct
->c_file
, ct
, "unable to open for reading");
1992 if ((digested
= ct
->c_digested
))
1993 MD5Init (&mdContext
);
2000 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2002 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2003 content_error (NULL
, ct
, "premature eof");
2007 if ((cc
= gotlen
) > len
)
2011 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2012 if (!isspace ((unsigned char) *ep
))
2016 for (; cp
< ep
; cp
++) {
2018 /* in an escape sequence */
2020 /* at byte 1 of an escape sequence */
2021 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2022 /* next is byte 2 */
2025 /* at byte 2 of an escape sequence */
2027 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2028 putc (mask
, ce
->ce_fp
);
2030 MD5Update (&mdContext
, &mask
, 1);
2031 if (ferror (ce
->ce_fp
)) {
2032 content_error (ce
->ce_file
, ct
, "error writing to");
2035 /* finished escape sequence; next may be literal or a new
2036 * escape sequence */
2039 /* on to next byte */
2043 /* not in an escape sequence */
2045 /* starting an escape sequence, or invalid '='? */
2046 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2047 /* "=\n" soft line break, eat the \n */
2051 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2052 /* We don't have 2 bytes left, so this is an invalid
2053 * escape sequence; just show the raw bytes (below). */
2054 } else if (isxdigit ((unsigned char) cp
[1]) &&
2055 isxdigit ((unsigned char) cp
[2])) {
2056 /* Next 2 bytes are hex digits, making this a valid escape
2057 * sequence; let's decode it (above). */
2061 /* One or both of the next 2 is out of range, making this
2062 * an invalid escape sequence; just show the raw bytes
2067 /* Just show the raw byte. */
2068 putc (*cp
, ce
->ce_fp
);
2071 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2073 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2076 if (ferror (ce
->ce_fp
)) {
2077 content_error (ce
->ce_file
, ct
, "error writing to");
2083 content_error (NULL
, ct
,
2084 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2088 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2090 if (fflush (ce
->ce_fp
)) {
2091 content_error (ce
->ce_file
, ct
, "error writing to");
2096 unsigned char digest
[16];
2098 MD5Final (digest
, &mdContext
);
2099 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2100 sizeof(digest
) / sizeof(digest
[0])))
2101 content_error (NULL
, ct
,
2102 "content integrity suspect (digest mismatch) -- continuing");
2105 fprintf (stderr
, "content integrity confirmed\n");
2108 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2111 *file
= ce
->ce_file
;
2117 return fileno (ce
->ce_fp
);
2120 free_encoding (ct
, 0);
2137 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2140 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2146 open7Bit (CT ct
, char **file
)
2148 int cc
, fd
, len
, own_ct_fp
= 0;
2149 char buffer
[BUFSIZ
];
2150 /* sbeck -- handle suffixes */
2153 CE ce
= &ct
->c_cefile
;
2156 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2161 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2162 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2168 if (*file
== NULL
) {
2171 ce
->ce_file
= mh_xstrdup(*file
);
2175 /* sbeck@cise.ufl.edu -- handle suffixes */
2177 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2178 if (ce
->ce_unlink
) {
2179 /* Create temporary file with filename extension. */
2180 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2181 adios(NULL
, "unable to create temporary file in %s",
2185 ce
->ce_file
= add (cp
, ce
->ce_file
);
2187 } else if (*file
== NULL
) {
2189 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2190 adios(NULL
, "unable to create temporary file in %s",
2193 ce
->ce_file
= mh_xstrdup(tempfile
);
2196 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2197 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2201 if (ct
->c_type
== CT_MULTIPART
) {
2202 CI ci
= &ct
->c_ctinfo
;
2206 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2207 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2208 + 1 + strlen (ci
->ci_subtype
);
2209 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2212 fputs (buffer
, ce
->ce_fp
);
2216 if (ci
->ci_comment
) {
2217 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2218 fputs ("\n\t", ce
->ce_fp
);
2222 putc (' ', ce
->ce_fp
);
2225 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2228 fprintf (ce
->ce_fp
, "\n");
2230 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2232 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2234 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2235 fprintf (ce
->ce_fp
, "\n");
2238 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2239 adios (NULL
, "internal error(3)");
2242 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2243 content_error (ct
->c_file
, ct
, "unable to open for reading");
2249 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2251 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2253 content_error (ct
->c_file
, ct
, "error reading from");
2257 content_error (NULL
, ct
, "premature eof");
2265 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2266 advise ("open7Bit", "fwrite");
2268 if (ferror (ce
->ce_fp
)) {
2269 content_error (ce
->ce_file
, ct
, "error writing to");
2274 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2276 if (fflush (ce
->ce_fp
)) {
2277 content_error (ce
->ce_file
, ct
, "error writing to");
2281 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2284 *file
= ce
->ce_file
;
2289 return fileno (ce
->ce_fp
);
2292 free_encoding (ct
, 0);
2306 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2308 char cachefile
[BUFSIZ
];
2311 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2316 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2317 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2323 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2324 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2325 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2326 ce
->ce_file
= mh_xstrdup(cachefile
);
2330 admonish (cachefile
, "unable to fopen for reading");
2333 *fd
= fileno (ce
->ce_fp
);
2337 *file
= ce
->ce_file
;
2338 *fd
= fileno (ce
->ce_fp
);
2349 return init_encoding (ct
, openFile
);
2354 openFile (CT ct
, char **file
)
2357 char cachefile
[BUFSIZ
];
2358 struct exbody
*e
= ct
->c_ctexbody
;
2359 CE ce
= &ct
->c_cefile
;
2361 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2373 content_error (NULL
, ct
, "missing name parameter");
2377 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2380 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2381 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2385 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2386 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2387 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2391 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2392 if ((fp
= fopen (cachefile
, "w"))) {
2394 char buffer
[BUFSIZ
];
2395 FILE *gp
= ce
->ce_fp
;
2397 fseek (gp
, 0L, SEEK_SET
);
2399 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2401 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2402 advise ("openFile", "fwrite");
2407 admonish (ce
->ce_file
, "error reading");
2408 (void) m_unlink (cachefile
);
2412 admonish (cachefile
, "error writing");
2413 (void) m_unlink (cachefile
);
2420 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2421 *file
= ce
->ce_file
;
2422 return fileno (ce
->ce_fp
);
2432 return init_encoding (ct
, openFTP
);
2437 openFTP (CT ct
, char **file
)
2439 int cachetype
, caching
, fd
;
2441 char *bp
, *ftp
, *user
, *pass
;
2442 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2444 CE ce
= &ct
->c_cefile
;
2445 static char *username
= NULL
;
2446 static char *password
= NULL
;
2450 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2456 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2467 if (!e
->eb_name
|| !e
->eb_site
) {
2468 content_error (NULL
, ct
, "missing %s parameter",
2469 e
->eb_name
? "site": "name");
2473 /* Get the buffer ready to go */
2475 buflen
= sizeof(buffer
);
2478 * Construct the query message for user
2480 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2486 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2492 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2493 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2498 if (e
->eb_size
> 0) {
2499 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2504 snprintf (bp
, buflen
, "? ");
2507 * Now, check the answer
2509 if (!read_yes_or_no_if_tty (buffer
))
2514 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2518 ruserpass (e
->eb_site
, &username
, &password
, 0);
2523 ce
->ce_unlink
= (*file
== NULL
);
2525 cachefile
[0] = '\0';
2526 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2527 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2528 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2529 if (*file
== NULL
) {
2536 ce
->ce_file
= mh_xstrdup(*file
);
2538 ce
->ce_file
= mh_xstrdup(cachefile
);
2541 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2542 adios(NULL
, "unable to create temporary file in %s",
2545 ce
->ce_file
= mh_xstrdup(tempfile
);
2548 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2549 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2554 int child_id
, i
, vecp
;
2558 vec
[vecp
++] = r1bindex (ftp
, '/');
2559 vec
[vecp
++] = e
->eb_site
;
2562 vec
[vecp
++] = e
->eb_dir
;
2563 vec
[vecp
++] = e
->eb_name
;
2564 vec
[vecp
++] = ce
->ce_file
,
2565 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2566 ? "ascii" : "binary";
2571 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2575 adios ("fork", "unable to");
2579 close (fileno (ce
->ce_fp
));
2581 fprintf (stderr
, "unable to exec ");
2587 if (pidXwait (child_id
, NULL
)) {
2588 username
= password
= NULL
;
2598 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2603 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2604 if ((fp
= fopen (cachefile
, "w"))) {
2606 FILE *gp
= ce
->ce_fp
;
2608 fseek (gp
, 0L, SEEK_SET
);
2610 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2612 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2613 advise ("openFTP", "fwrite");
2618 admonish (ce
->ce_file
, "error reading");
2619 (void) m_unlink (cachefile
);
2623 admonish (cachefile
, "error writing");
2624 (void) m_unlink (cachefile
);
2632 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2633 *file
= ce
->ce_file
;
2634 return fileno (ce
->ce_fp
);
2645 return init_encoding (ct
, openMail
);
2650 openMail (CT ct
, char **file
)
2652 int child_id
, fd
, i
, vecp
;
2654 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2655 struct exbody
*e
= ct
->c_ctexbody
;
2656 CE ce
= &ct
->c_cefile
;
2658 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2669 if (!e
->eb_server
) {
2670 content_error (NULL
, ct
, "missing server parameter");
2674 /* Get buffer ready to go */
2676 buflen
= sizeof(buffer
);
2678 /* Now, construct query message */
2679 snprintf (bp
, buflen
, "Retrieve content");
2685 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2691 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2693 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2695 /* Now, check answer */
2696 if (!read_yes_or_no_if_tty (buffer
))
2700 vec
[vecp
++] = r1bindex (mailproc
, '/');
2701 vec
[vecp
++] = e
->eb_server
;
2702 vec
[vecp
++] = "-subject";
2703 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2704 vec
[vecp
++] = "-body";
2705 vec
[vecp
++] = e
->eb_body
;
2708 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2712 advise ("fork", "unable to");
2716 execvp (mailproc
, vec
);
2717 fprintf (stderr
, "unable to exec ");
2723 if (pidXwait (child_id
, NULL
) == OK
)
2724 advise (NULL
, "request sent");
2728 if (*file
== NULL
) {
2730 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2731 adios(NULL
, "unable to create temporary file in %s",
2734 ce
->ce_file
= mh_xstrdup(tempfile
);
2737 ce
->ce_file
= mh_xstrdup(*file
);
2741 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2742 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2746 /* showproc is for mhshow and mhstore, though mhlist -debug
2747 * prints it, too. */
2748 mh_xfree(ct
->c_showproc
);
2749 ct
->c_showproc
= mh_xstrdup("true");
2751 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2752 *file
= ce
->ce_file
;
2753 return fileno (ce
->ce_fp
);
2764 return init_encoding (ct
, openURL
);
2769 openURL (CT ct
, char **file
)
2771 struct exbody
*e
= ct
->c_ctexbody
;
2772 CE ce
= &ct
->c_cefile
;
2773 char *urlprog
, *program
;
2774 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2775 int fd
, caching
, cachetype
;
2776 struct msgs_array args
= { 0, 0, NULL
};
2779 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2783 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2787 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2799 content_error(NULL
, ct
, "missing url parameter");
2803 ce
->ce_unlink
= (*file
== NULL
);
2805 cachefile
[0] = '\0';
2807 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2808 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2809 if (*file
== NULL
) {
2816 ce
->ce_file
= mh_xstrdup(*file
);
2818 ce
->ce_file
= mh_xstrdup(cachefile
);
2821 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2822 adios(NULL
, "unable to create temporary file in %s",
2825 ce
->ce_file
= mh_xstrdup(tempfile
);
2828 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2829 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2833 switch (child_id
= fork()) {
2835 adios ("fork", "unable to");
2839 argsplit_msgarg(&args
, urlprog
, &program
);
2840 app_msgarg(&args
, e
->eb_url
);
2841 app_msgarg(&args
, NULL
);
2842 dup2(fileno(ce
->ce_fp
), 1);
2843 close(fileno(ce
->ce_fp
));
2844 execvp(program
, args
.msgs
);
2845 fprintf(stderr
, "Unable to exec ");
2851 if (pidXwait(child_id
, NULL
)) {
2859 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2864 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2865 if ((fp
= fopen(cachefile
, "w"))) {
2867 FILE *gp
= ce
->ce_fp
;
2869 fseeko(gp
, 0, SEEK_SET
);
2871 while ((cc
= fread(buffer
, sizeof(*buffer
),
2872 sizeof(buffer
), gp
)) > 0)
2873 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2874 advise ("openURL", "fwrite");
2880 admonish(ce
->ce_file
, "error reading");
2881 (void) m_unlink (cachefile
);
2888 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2889 *file
= ce
->ce_file
;
2895 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2896 * has to be base64 decoded.
2899 readDigest (CT ct
, char *cp
)
2901 unsigned char *digest
;
2904 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2905 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2907 if (strlen ((char *) digest
) <= maxlen
) {
2908 memcpy (ct
->c_digest
, digest
, maxlen
);
2913 fprintf (stderr
, "MD5 digest=");
2914 for (i
= 0; i
< maxlen
; ++i
) {
2915 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2917 fprintf (stderr
, "\n");
2923 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2924 (int) strlen ((char *) digest
));
2934 /* Multipart parts might have content before the first subpart and/or
2935 after the last subpart that hasn't been stored anywhere else, so do
2938 get_leftover_mp_content (CT ct
, int before
/* or after */)
2940 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2942 int found_boundary
= 0;
2948 char *content
= NULL
;
2950 if (! m
) return NOTOK
;
2953 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2955 /* Isolate the beginning of this part to the beginning of the
2956 first subpart and save any content between them. */
2957 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2958 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2959 boundary
= concat ("--", m
->mp_start
, NULL
);
2961 struct part
*last_subpart
= NULL
;
2962 struct part
*subpart
;
2964 /* Go to the last subpart to get its end position. */
2965 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2966 last_subpart
= subpart
;
2969 if (last_subpart
== NULL
) return NOTOK
;
2971 /* Isolate the end of the last subpart to the end of this part
2972 and save any content between them. */
2973 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2974 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2975 boundary
= concat ("--", m
->mp_stop
, NULL
);
2978 /* Back up by 1 to pick up the newline. */
2979 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2981 /* Don't look beyond beginning of first subpart (before) or
2982 next part (after). */
2983 if (read
> max
) bufp
[read
-max
] = '\0';
2986 if (! strcmp (bufp
, boundary
)) {
2990 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2996 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2998 char *old_content
= content
;
2999 content
= concat (content
, bufp
, NULL
);
3003 ? concat ("\n", bufp
, NULL
)
3004 : concat (bufp
, NULL
);
3009 if (found_boundary
|| read
> max
) break;
3011 if (read
> max
) break;
3015 /* Skip the newline if that's all there is. */
3019 /* Remove trailing newline, except at EOF. */
3020 if ((before
|| ! feof (ct
->c_fp
)) &&
3021 (cp
= content
+ strlen (content
)) > content
&&
3026 if (strlen (content
) > 1) {
3028 m
->mp_content_before
= content
;
3030 m
->mp_content_after
= content
;
3045 ct_type_str (int type
) {
3047 case CT_APPLICATION
:
3048 return "application";
3064 return "unknown_type";
3070 ct_subtype_str (int type
, int subtype
) {
3072 case CT_APPLICATION
:
3074 case APPLICATION_OCTETS
:
3076 case APPLICATION_POSTSCRIPT
:
3077 return "postscript";
3079 return "unknown_app_subtype";
3083 case MESSAGE_RFC822
:
3085 case MESSAGE_PARTIAL
:
3087 case MESSAGE_EXTERNAL
:
3090 return "unknown_msg_subtype";
3096 case MULTI_ALTERNATE
:
3097 return "alternative";
3100 case MULTI_PARALLEL
:
3105 return "unknown_multipart_subtype";
3116 return "unknown_text_subtype";
3119 return "unknown_type";
3125 ct_str_type (const char *type
) {
3126 struct str2init
*s2i
;
3128 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3129 if (! strcasecmp (type
, s2i
->si_key
)) {
3133 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3142 ct_str_subtype (int type
, const char *subtype
) {
3146 case CT_APPLICATION
:
3147 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3148 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3152 return kv
->kv_value
;
3154 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3155 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3159 return kv
->kv_value
;
3161 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3162 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3166 return kv
->kv_value
;
3168 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3169 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3173 return kv
->kv_value
;
3180 /* Find the content type and InitFunc for the CT. */
3181 const struct str2init
*
3182 get_ct_init (int type
) {
3183 const struct str2init
*sp
;
3185 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3186 if (type
== sp
->si_val
) {
3195 ce_str (int encoding
) {
3200 return "quoted-printable";
3216 /* Find the content type and InitFunc for the content encoding method. */
3217 const struct str2init
*
3218 get_ce_method (const char *method
) {
3219 struct str2init
*sp
;
3221 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3222 if (! strcasecmp (method
, sp
->si_key
)) {
3231 * Parse a series of MIME attributes (or parameters) given a header as
3234 * Arguments include:
3236 * filename - Name of input file (for error messages)
3237 * fieldname - Name of field being processed
3238 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3239 * Updated to point to end of attributes when finished.
3240 * param_head - Pointer to head of parameter list
3241 * param_tail - Pointer to tail of parameter list
3242 * commentp - Pointer to header comment pointer (may be NULL)
3244 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3245 * DONE to indicate a benign error (minor parsing error, but the program
3250 parse_header_attrs (const char *filename
, const char *fieldname
,
3251 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3254 char *cp
= *header_attrp
;
3260 struct sectlist
*next
;
3266 struct sectlist
*sechead
;
3267 struct parmlist
*next
;
3268 } *pp
, *pp2
, *phead
= NULL
;
3270 while (*cp
== ';') {
3271 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3272 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3275 while (isspace ((unsigned char) *cp
))
3279 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3284 if (! suppress_extraneous_trailing_semicolon_warning
) {
3286 "extraneous trailing ';' in message %s's %s: "
3288 filename
, fieldname
);
3293 /* down case the attribute name */
3294 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3295 *dp
= tolower ((unsigned char) *dp
);
3297 for (up
= dp
; isspace ((unsigned char) *dp
);)
3299 if (dp
== cp
|| *dp
!= '=') {
3301 "invalid parameter in message %s's %s: "
3302 "field\n%*sparameter %s (error detected at offset %d)",
3303 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3308 * To handle RFC 2231, we have to deal with the following extensions:
3310 * name*=encoded-value
3311 * name*<N>=part-N-of-a-parameter-value
3312 * name*<N>*=encoded-part-N-of-a-parameter-value
3315 * If there's a * right before the equal sign, it's encoded.
3316 * If there's a * and one or more digits, then it's section N.
3318 * Remember we can have one or the other, or both. cp points to
3319 * beginning of name, up points past the last character in the
3323 for (vp
= cp
; vp
< up
; vp
++) {
3324 if (*vp
== '*' && vp
< up
- 1) {
3327 } else if (*vp
== '*' && vp
== up
- 1) {
3329 } else if (partial
) {
3330 if (isdigit((unsigned char) *vp
))
3331 index
= *vp
- '0' + index
* 10;
3333 advise (NULL
, "invalid parameter index in message %s's "
3334 "%s: field\n%*s(parameter %s)", filename
,
3335 fieldname
, strlen(invo_name
) + 2, "", cp
);
3344 * Break out the parameter name and value sections and allocate
3348 nameptr
= mh_xmalloc(len
+ 1);
3349 strncpy(nameptr
, cp
, len
);
3350 nameptr
[len
] = '\0';
3352 for (dp
++; isspace ((unsigned char) *dp
);)
3357 * Single quotes delimit the character set and language tag.
3358 * They are required on the first section (or a complete
3363 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3369 charset
= mh_xmalloc(len
+ 1);
3370 strncpy(charset
, dp
, len
);
3371 charset
[len
] = '\0';
3377 advise(NULL
, "missing charset in message %s's %s: "
3378 "field\n%*s(parameter %s)", filename
, fieldname
,
3379 strlen(invo_name
) + 2, "", nameptr
);
3385 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3392 lang
= mh_xmalloc(len
+ 1);
3393 strncpy(lang
, dp
, len
);
3400 advise(NULL
, "missing language tag in message %s's %s: "
3401 "field\n%*s(parameter %s)", filename
, fieldname
,
3402 strlen(invo_name
) + 2, "", nameptr
);
3412 * At this point vp should be pointing at the beginning
3413 * of the encoded value/section. Continue until we reach
3414 * the end or get whitespace. But first, calculate the
3415 * length so we can allocate the correct buffer size.
3418 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3420 if (*(vp
+ 1) == '\0' ||
3421 !isxdigit((unsigned char) *(vp
+ 1)) ||
3422 *(vp
+ 2) == '\0' ||
3423 !isxdigit((unsigned char) *(vp
+ 2))) {
3424 advise(NULL
, "invalid encoded sequence in message "
3425 "%s's %s: field\n%*s(parameter %s)",
3426 filename
, fieldname
, strlen(invo_name
) + 2,
3438 up
= valptr
= mh_xmalloc(len
+ 1);
3440 for (vp
= dp
; istoken(*vp
); vp
++) {
3442 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3453 * A "normal" string. If it's got a leading quote, then we
3454 * strip the quotes out. Otherwise go until we reach the end
3455 * or get whitespace. Note we scan it twice; once to get the
3456 * length, then the second time copies it into the destination
3463 for (cp
= dp
+ 1;;) {
3468 "invalid quoted-string in message %s's %s: "
3469 "field\n%*s(parameter %s)",
3470 filename
, fieldname
, strlen(invo_name
) + 2, "",
3491 for (cp
= dp
; istoken (*cp
); cp
++) {
3496 valptr
= mh_xmalloc(len
+ 1);
3500 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3508 strncpy(valptr
, cp
= dp
, len
);
3516 * If 'partial' is set, we don't allocate a parameter now. We
3517 * put it on the parameter linked list to be reassembled later.
3519 * "phead" points to a list of all parameters we need to reassemble.
3520 * Each parameter has a list of sections. We insert the sections in
3525 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3526 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3541 * Insert this into the section linked list
3549 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3550 sp
->next
= pp
->sechead
;
3553 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3554 if (sp2
->index
== sp
->index
) {
3555 advise (NULL
, "duplicate index (%d) in message "
3556 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3557 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, "",
3579 * Save our charset and lang tags.
3582 if (index
== 0 && encoded
) {
3583 mh_xfree(pp
->charset
);
3584 pp
->charset
= charset
;
3589 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3590 pm
->pm_charset
= charset
;
3594 while (isspace ((unsigned char) *cp
))
3598 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3604 * Now that we're done, reassemble all of the partial parameters.
3607 for (pp
= phead
; pp
!= NULL
; ) {
3611 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3612 if (sp
->index
!= pindex
++) {
3613 advise(NULL
, "missing section %d for parameter in "
3614 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3615 filename
, fieldname
, strlen(invo_name
) + 2, "",
3622 p
= q
= mh_xmalloc(tlen
+ 1);
3623 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3624 memcpy(q
, sp
->value
, sp
->len
);
3634 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3635 pm
->pm_charset
= pp
->charset
;
3636 pm
->pm_lang
= pp
->lang
;
3647 * Return the charset for a particular content type.
3651 content_charset (CT ct
) {
3652 char *ret_charset
= NULL
;
3654 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3656 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3661 * Create a string based on a list of output parameters. Assume that this
3662 * parameter string will be appended to an existing header, so start out
3663 * with the separator (;). Perform RFC 2231 encoding when necessary.
3667 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3669 char *paramout
= NULL
;
3670 char line
[CPERLIN
* 2], *q
;
3671 int curlen
, index
, cont
, encode
, i
;
3672 size_t valoff
, numchars
;
3674 while (params
!= NULL
) {
3680 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3683 if (strlen(params
->pm_name
) > CPERLIN
) {
3684 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3689 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3692 * Loop until we get a parameter that fits within a line. We
3693 * assume new lines start with a tab, so check our overflow based
3703 * At this point we're definitely continuing the line, so
3704 * be sure to include the parameter name and section index.
3707 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3708 params
->pm_name
, index
);
3711 * Both of these functions do a NUL termination
3715 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3716 numchars
, valoff
, index
);
3718 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3728 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3733 * "line" starts with a ;\n\t, so that doesn't count against
3734 * the length. But add 8 since it starts with a tab; that's
3735 * how we end up with 5.
3738 initialwidth
= strlen(line
) + 5;
3741 * At this point the line should be built, so add it to our
3742 * current output buffer.
3745 paramout
= add(line
, paramout
);
3749 * If this won't fit on the line, start a new one. Save room in
3750 * case we need a semicolon on the end
3753 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3765 * At this point, we're either finishing a contined parameter, or
3766 * we're working on a new one.
3770 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3771 params
->pm_name
, index
);
3773 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3778 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3779 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3781 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3782 strlen(params
->pm_value
+ valoff
), valoff
);
3789 paramout
= add(line
, paramout
);
3790 initialwidth
+= strlen(line
);
3792 params
= params
->pm_next
;
3796 *offsetout
= initialwidth
;
3802 * Calculate the size of a parameter.
3806 * pm - The parameter being output
3807 * index - If continuing the parameter, the index of the section
3809 * valueoff - The current offset into the parameter value that we're
3810 * working on (previous sections have consumed valueoff bytes).
3811 * encode - Set if we should perform encoding on this parameter section
3812 * (given that we're consuming bytesfit bytes).
3813 * cont - Set if the remaining data in value will not fit on a single
3814 * line and will need to be continued.
3815 * bytesfit - The number of bytes that we can consume from the parameter
3816 * value and still fit on a completely new line. The
3817 * calculation assumes the new line starts with a tab,
3818 * includes the parameter name and any encoding, and fits
3819 * within CPERLIN bytes. Will always be at least 1.
3823 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3826 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3827 size_t len
= 0, fit
= 0;
3828 int fitlimit
= 0, eightbit
, maxfit
;
3833 * Add up the length. First, start with the parameter name.
3836 len
= strlen(pm
->pm_name
);
3839 * Scan the parameter value and see if we need to do encoding for this
3843 eightbit
= contains8bit(start
, NULL
);
3846 * Determine if we need to encode this section. Encoding is necessary if:
3848 * - There are any 8-bit characters at all and we're on the first
3850 * - There are 8-bit characters within N bytes of our section start.
3851 * N is calculated based on the number of bytes it would take to
3852 * reach CPERLIN. Specifically:
3853 * 8 (starting tab) +
3854 * strlen(param name) +
3855 * 4 ('* for section marker, '=', opening/closing '"')
3857 * is the number of bytes used by everything that isn't part of the
3858 * value. So that gets subtracted from CPERLIN.
3861 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3862 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3863 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3867 len
++; /* Add in equal sign */
3871 * We're using maxfit as a marker for how many characters we can
3872 * fit into the line. Bump it by two because we're not using quotes
3879 * If we don't have a charset or language tag in this parameter,
3883 if (! pm
->pm_charset
) {
3884 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3885 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3886 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3887 "local character set is US-ASCII", pm
->pm_name
);
3890 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3892 len
++; /* For the encoding marker */
3895 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3900 * We know we definitely need to include an index. maxfit already
3901 * includes the section marker.
3903 len
+= strlen(indexchar
);
3905 for (p
= start
; *p
!= '\0'; p
++) {
3906 if (isparamencode(*p
)) {
3914 * Just so there's no confusion: maxfit is counting OUTPUT
3915 * characters (post-encoding). fit is counting INPUT characters.
3917 if (! fitlimit
&& maxfit
>= 0)
3919 else if (! fitlimit
)
3924 * Calculate the string length, but add room for quoting \
3925 * and " if necessary. Also account for quotes at beginning
3928 for (p
= start
; *p
!= '\0'; p
++) {
3939 if (! fitlimit
&& maxfit
>= 0)
3941 else if (! fitlimit
)
3958 * Output an encoded parameter string.
3962 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3963 size_t valueoff
, int index
)
3965 size_t outlen
= 0, n
;
3966 char *endptr
= output
+ len
, *p
;
3969 * First, output the marker for an encoded string.
3977 * If the index is 0, output the character set and language tag.
3978 * If theses were NULL, they should have already been filled in
3983 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3987 if (output
> endptr
) {
3988 advise(NULL
, "Internal error: parameter buffer overflow");
3994 * Copy over the value, encoding if necessary
3997 p
= pm
->pm_value
+ valueoff
;
3998 while (valuelen
-- > 0) {
3999 if (isparamencode(*p
)) {
4000 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4007 if (output
> endptr
) {
4008 advise(NULL
, "Internal error: parameter buffer overflow");
4019 * Output a "normal" parameter, without encoding. Be sure to escape
4020 * quotes and backslashes if necessary.
4024 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4028 char *endptr
= output
+ len
, *p
;
4034 p
= pm
->pm_value
+ valueoff
;
4036 while (valuelen
-- > 0) {
4046 if (output
> endptr
) {
4047 advise(NULL
, "Internal error: parameter buffer overflow");
4052 if (output
- 2 > endptr
) {
4053 advise(NULL
, "Internal error: parameter buffer overflow");
4064 * Add a parameter to the parameter linked list
4068 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4073 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4074 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4077 (*last
)->pm_next
= pm
;
4088 * Either replace a current parameter with a new value, or add the parameter
4089 * to the parameter linked list.
4093 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4097 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4098 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4100 * If nocopy is set, it's assumed that we own both name
4101 * and value. We don't need name, so we discard it now.
4106 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4111 return add_param(first
, last
, name
, value
, nocopy
);
4115 * Retrieve a parameter value from a parameter linked list. If the parameter
4116 * value needs converted to the local character set, do that now.
4120 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4122 while (first
!= NULL
) {
4123 if (strcasecmp(name
, first
->pm_name
) == 0) {
4125 return first
->pm_value
;
4126 return getcpy(get_param_value(first
, replace
));
4128 first
= first
->pm_next
;
4135 * Return a parameter value, converting to the local character set if
4139 char *get_param_value(PM pm
, char replace
)
4141 static char buffer
[4096]; /* I hope no parameters are larger */
4142 size_t bufsize
= sizeof(buffer
);
4147 ICONV_CONST
char *p
;
4148 #else /* HAVE_ICONV */
4150 #endif /* HAVE_ICONV */
4155 * If we don't have a character set indicated, it's assumed to be
4156 * US-ASCII. If it matches our character set, we don't need to convert
4160 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4161 strlen(pm
->pm_charset
))) {
4162 return pm
->pm_value
;
4166 * In this case, we need to convert. If we have iconv support, use
4167 * that. Otherwise, go through and simply replace every non-ASCII
4168 * character with the substitution character.
4173 bufsize
= sizeof(buffer
);
4174 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4176 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4177 if (cd
== (iconv_t
) -1) {
4181 inbytes
= strlen(pm
->pm_value
);
4185 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4186 if (errno
!= EILSEQ
) {
4191 * Reset shift state, substitute our character,
4192 * try to restart conversion.
4195 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4208 for (++p
, --inbytes
;
4209 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4228 #endif /* HAVE_ICONV */
4231 * Take everything non-ASCII and substituite the replacement character
4235 bufsize
= sizeof(buffer
);
4236 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4237 /* FIXME: !iscntrl should perhaps be isprint as that allows all
4238 * classes bar cntrl, whereas the cntrl class can include those
4239 * in space and blank.
4240 * http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html */
4241 if (isascii((unsigned char) *p
) && !iscntrl((unsigned char) *p
))