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
)
214 bogus_mp_content
= 0;
217 * Check if file is actually standard input
219 if ((is_stdin
= !(strcmp (file
, "-")))) {
220 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
222 advise("mhparse", "unable to create temporary file in %s",
226 file
= mh_xstrdup(tfile
);
228 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
229 if (fwrite(buffer
, 1, n
, fp
) != n
) {
230 (void) m_unlink (file
);
231 advise (file
, "error copying to temporary file");
237 if (ferror (stdin
)) {
238 (void) m_unlink (file
);
239 advise ("stdin", "error reading");
243 (void) m_unlink (file
);
244 advise (file
, "error writing");
247 fseek (fp
, 0L, SEEK_SET
);
248 } else if (lstat (file
, &statbuf
) == NOTOK
) {
249 advise (file
, "unable to lstat");
251 } else if (S_ISDIR(statbuf
.st_mode
)) {
252 /* Don't try to parse a directory. */
253 advise (NULL
, "%s is a directory", file
);
255 } else if ((fp
= fopen (file
, "r")) == NULL
) {
256 advise (file
, "unable to read");
260 if (!(ct
= get_content (fp
, file
, 1))) {
262 (void) m_unlink (file
);
263 advise (NULL
, "unable to decode %s", file
);
268 ct
->c_unlink
= 1; /* temp file to remove */
272 if (ct
->c_end
== 0L) {
273 fseek (fp
, 0L, SEEK_END
);
274 ct
->c_end
= ftell (fp
);
277 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
289 * Main routine for reading/parsing the headers
290 * of a message content.
292 * toplevel = 1 # we are at the top level of the message
293 * toplevel = 0 # we are inside message type or multipart type
294 * # other than multipart/digest
295 * toplevel = -1 # we are inside multipart/digest
296 * NB: on failure we will fclose(in)!
300 get_content (FILE *in
, char *file
, int toplevel
)
303 char buf
[BUFSIZ
], name
[NAMESZ
];
307 m_getfld_state_t gstate
= 0;
309 /* allocate the content structure */
312 ct
->c_file
= add (file
, NULL
);
313 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
316 * Parse the header fields for this
317 * content into a linked list.
319 m_getfld_track_filepos (&gstate
, in
);
320 for (compnum
= 1;;) {
321 int bufsz
= sizeof buf
;
322 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
327 /* get copies of the buffers */
328 np
= mh_xstrdup(name
);
329 vp
= mh_xstrdup(buf
);
331 /* if necessary, get rest of field */
332 while (state
== FLDPLUS
) {
334 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
335 vp
= add (buf
, vp
); /* add to previous value */
338 /* Now add the header data to the list */
339 add_header (ct
, np
, vp
);
341 /* continue, to see if this isn't the last header field */
342 ct
->c_begin
= ftell (in
) + 1;
346 if (name
[0] == ':') {
347 /* Special case: no blank line between header and body. The
348 file position indicator is on the newline at the end of the
349 line, but it needs to be one prior to the beginning of the
350 line. So subtract the length of the line, bufsz, plus 1. */
351 ct
->c_begin
= ftell (in
) - (bufsz
+ 1);
353 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
358 ct
->c_begin
= ftell (in
);
363 adios (NULL
, "message format error in component #%d", compnum
);
366 adios (NULL
, "getfld() returned %d", state
);
369 /* break out of the loop */
372 m_getfld_state_destroy (&gstate
);
375 * Read the content headers. We will parse the
376 * MIME related header fields into their various
377 * structures and set internal flags related to
378 * content type/subtype, etc.
381 hp
= ct
->c_first_hf
; /* start at first header field */
383 /* Get MIME-Version field */
384 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
389 vrsn
= add (hp
->value
, NULL
);
391 /* Now, cleanup this field */
394 while (isspace ((unsigned char) *cp
))
396 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
398 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
399 if (!isspace ((unsigned char) *dp
))
403 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
406 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
409 for (dp
= cp
; istoken (*dp
); dp
++)
413 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
416 admonish (NULL
, "message %s has unknown value for %s: field (%s)",
417 ct
->c_file
, VRSN_FIELD
, cp
);
422 if (! suppress_multiple_mime_version_warning
)
423 advise (NULL
, "message %s has multiple %s: fields",
424 ct
->c_file
, VRSN_FIELD
);
428 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
429 /* Get Content-Type field */
430 struct str2init
*s2i
;
431 CI ci
= &ct
->c_ctinfo
;
433 /* Check if we've already seen a Content-Type header */
435 advise (NULL
, "message %s has multiple %s: fields",
436 ct
->c_file
, TYPE_FIELD
);
440 /* Parse the Content-Type field */
441 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
445 * Set the Init function and the internal
446 * flag for this content type.
448 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
449 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
451 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
453 ct
->c_type
= s2i
->si_val
;
454 ct
->c_ctinitfnx
= s2i
->si_init
;
456 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
457 /* Get Content-Transfer-Encoding field */
459 struct str2init
*s2i
;
462 * Check if we've already seen the
463 * Content-Transfer-Encoding field
466 advise (NULL
, "message %s has multiple %s: fields",
467 ct
->c_file
, ENCODING_FIELD
);
471 /* get copy of this field */
472 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
474 while (isspace ((unsigned char) *cp
))
476 for (dp
= cp
; istoken (*dp
); dp
++)
482 * Find the internal flag and Init function
483 * for this transfer encoding.
485 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
486 if (!strcasecmp (cp
, s2i
->si_key
))
488 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
491 ct
->c_encoding
= s2i
->si_val
;
493 /* Call the Init function for this encoding */
494 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
497 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
498 /* Get Content-MD5 field */
504 if (ct
->c_digested
) {
505 advise (NULL
, "message %s has multiple %s: fields",
506 ct
->c_file
, MD5_FIELD
);
510 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
512 while (isspace ((unsigned char) *cp
))
514 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
516 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
517 if (!isspace ((unsigned char) *dp
))
521 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
524 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
529 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
537 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
538 /* Get Content-ID field */
539 ct
->c_id
= add (hp
->value
, ct
->c_id
);
541 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
542 /* Get Content-Description field */
543 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
545 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
546 /* Get Content-Disposition field */
547 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
552 hp
= hp
->next
; /* next header field */
556 * Check if we saw a Content-Type field.
557 * If not, then assign a default value for
558 * it, and the Init function.
562 * If we are inside a multipart/digest message,
563 * so default type is message/rfc822
566 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
568 ct
->c_type
= CT_MESSAGE
;
569 ct
->c_ctinitfnx
= InitMessage
;
572 * Else default type is text/plain
574 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
576 ct
->c_type
= CT_TEXT
;
577 ct
->c_ctinitfnx
= InitText
;
581 /* Use default Transfer-Encoding, if necessary */
583 ct
->c_encoding
= CE_7BIT
;
596 * small routine to add header field to list
600 add_header (CT ct
, char *name
, char *value
)
604 /* allocate header field structure */
607 /* link data into header structure */
612 /* link header structure into the list */
613 if (ct
->c_first_hf
== NULL
) {
614 ct
->c_first_hf
= hp
; /* this is the first */
617 ct
->c_last_hf
->next
= hp
; /* add it to the end */
626 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
627 * directives. Fills in the information of the CTinfo structure.
630 get_ctinfo (char *cp
, CT ct
, int magic
)
639 /* store copy of Content-Type line */
640 cp
= ct
->c_ctline
= add (cp
, NULL
);
642 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
645 /* change newlines to spaces */
646 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
649 /* trim trailing spaces */
650 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
651 if (!isspace ((unsigned char) *dp
))
656 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
658 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
659 &ci
->ci_comment
) == NOTOK
)
662 for (dp
= cp
; istoken (*dp
); dp
++)
665 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
669 advise (NULL
, "invalid %s: field in message %s (empty type)",
670 TYPE_FIELD
, ct
->c_file
);
673 to_lower(ci
->ci_type
);
675 while (isspace ((unsigned char) *cp
))
678 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
679 &ci
->ci_comment
) == NOTOK
)
684 ci
->ci_subtype
= mh_xstrdup("");
689 while (isspace ((unsigned char) *cp
))
692 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
693 &ci
->ci_comment
) == NOTOK
)
696 for (dp
= cp
; istoken (*dp
); dp
++)
699 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
702 if (!*ci
->ci_subtype
) {
704 "invalid %s: field in message %s (empty subtype for \"%s\")",
705 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
708 to_lower(ci
->ci_subtype
);
711 while (isspace ((unsigned char) *cp
))
714 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
715 &ci
->ci_comment
) == NOTOK
)
718 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
719 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
720 &ci
->ci_comment
)) != OK
) {
721 return status
== NOTOK
? NOTOK
: OK
;
725 * Get any <Content-Id> given in buffer
727 if (magic
&& *cp
== '<') {
730 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
731 advise (NULL
, "invalid ID in message %s", ct
->c_file
);
737 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
743 while (isspace ((unsigned char) *cp
))
748 * Get any [Content-Description] given in buffer.
750 if (magic
&& *cp
== '[') {
752 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
756 advise (NULL
, "invalid description in message %s", ct
->c_file
);
764 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
770 while (isspace ((unsigned char) *cp
))
775 * Get any {Content-Disposition} given in buffer.
777 if (magic
&& *cp
== '{') {
779 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
783 advise (NULL
, "invalid disposition in message %s", ct
->c_file
);
791 if (get_dispo(cp
, ct
, 1) != OK
)
797 while (isspace ((unsigned char) *cp
))
802 * Get any extension directives (right now just the content transfer
803 * encoding, but maybe others) that we care about.
806 if (magic
&& *cp
== '*') {
808 * See if it's a CTE we match on
813 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
817 advise (NULL
, "invalid null transfer encoding specification");
824 ct
->c_reqencoding
= CE_UNKNOWN
;
826 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
827 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
828 ct
->c_reqencoding
= kv
->kv_value
;
833 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
834 advise (NULL
, "invalid CTE specification: \"%s\"", dp
);
838 while (isspace ((unsigned char) *cp
))
843 * Check if anything is left over
847 ci
->ci_magic
= mh_xstrdup(cp
);
849 /* If there is a Content-Disposition header and it doesn't
850 have a *filename=, extract it from the magic contents.
851 The r1bindex call skips any leading directory
853 if (ct
->c_dispo_type
&&
854 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
855 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
856 r1bindex(ci
->ci_magic
, '/'), 0);
861 "extraneous information in message %s's %s: field\n%*s(%s)",
862 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
870 * Parse out a Content-Disposition header. A lot of this is cribbed from
874 get_dispo (char *cp
, CT ct
, int buildflag
)
876 char *dp
, *dispoheader
;
881 * Save the whole copy of the Content-Disposition header, unless we're
882 * processing a mhbuild directive. A NULL c_dispo will be a flag to
883 * mhbuild that the disposition header needs to be generated at that
887 dispoheader
= cp
= add(cp
, NULL
);
889 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
892 /* change newlines to spaces */
893 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
896 /* trim trailing spaces */
897 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
898 if (!isspace ((unsigned char) *dp
))
903 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
905 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
911 for (dp
= cp
; istoken (*dp
); dp
++)
914 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
917 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
920 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
921 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
923 if (status
== NOTOK
) {
929 "extraneous information in message %s's %s: field\n%*s(%s)",
930 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
936 ct
->c_dispo
= dispoheader
;
943 get_comment (const char *filename
, const char *fieldname
, char **ap
,
948 char c
, buffer
[BUFSIZ
], *dp
;
958 advise (NULL
, "invalid comment in message %s's %s: field",
959 filename
, fieldname
);
964 if ((c
= *cp
++) == '\0')
987 if ((dp
= *commentp
)) {
988 *commentp
= concat (dp
, " ", buffer
, NULL
);
991 *commentp
= mh_xstrdup(buffer
);
995 while (isspace ((unsigned char) *cp
))
1006 * Handles content types audio, image, and video.
1007 * There's not much to do right here.
1015 return OK
; /* not much to do here */
1026 char buffer
[BUFSIZ
];
1031 CI ci
= &ct
->c_ctinfo
;
1033 /* check for missing subtype */
1034 if (!*ci
->ci_subtype
)
1035 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1038 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1040 /* allocate text character set structure */
1042 ct
->c_ctparams
= (void *) t
;
1044 /* scan for charset parameter */
1045 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1046 if (!strcasecmp (pm
->pm_name
, "charset"))
1049 /* check if content specified a character set */
1051 chset
= pm
->pm_value
;
1052 t
->tx_charset
= CHARSET_SPECIFIED
;
1054 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1058 * If we can not handle character set natively,
1059 * then check profile for string to modify the
1060 * terminal or display method.
1062 * termproc is for mhshow, though mhlist -debug prints it, too.
1064 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1065 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1066 if ((cp
= context_find (buffer
)))
1067 ct
->c_termproc
= mh_xstrdup(cp
);
1079 InitMultiPart (CT ct
)
1089 struct multipart
*m
;
1090 struct part
*part
, **next
;
1091 CI ci
= &ct
->c_ctinfo
;
1096 * The encoding for multipart messages must be either
1097 * 7bit, 8bit, or binary (per RFC2045).
1099 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1100 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1101 /* Copy the Content-Transfer-Encoding header field body so we can
1102 remove any trailing whitespace and leading blanks from it. */
1103 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1105 bp
= cte
+ strlen (cte
) - 1;
1106 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1107 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1110 "\"%s/%s\" type in message %s must be encoded in\n"
1111 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1112 "mhfixmsg -fixcte can fix it, or\n"
1113 "manually edit the file and change the \"%s\"\n"
1114 "Content-Transfer-Encoding to one of those. For now",
1115 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1122 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1125 * Check for "boundary" parameter, which is
1126 * required for multipart messages.
1129 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1130 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1136 /* complain if boundary parameter is missing */
1139 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1140 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1144 /* allocate primary structure for multipart info */
1146 ct
->c_ctparams
= (void *) m
;
1148 /* check if boundary parameter contains only whitespace characters */
1149 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1152 advise (NULL
, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1153 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1157 /* remove trailing whitespace from boundary parameter */
1158 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1159 if (!isspace ((unsigned char) *dp
))
1163 /* record boundary separators */
1164 m
->mp_start
= concat (bp
, "\n", NULL
);
1165 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1167 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1168 advise (ct
->c_file
, "unable to open for reading");
1172 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1174 next
= &m
->mp_parts
;
1178 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1183 if (bufp
[0] != '-' || bufp
[1] != '-')
1186 if (strcmp (bufp
+ 2, m
->mp_start
))
1191 next
= &part
->mp_next
;
1193 if (!(p
= get_content (fp
, ct
->c_file
,
1194 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1202 fseek (fp
, pos
, SEEK_SET
);
1205 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1209 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1210 if (p
->c_end
< p
->c_begin
)
1211 p
->c_begin
= p
->c_end
;
1216 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1221 if (! suppress_bogus_mp_content_warning
) {
1222 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1224 bogus_mp_content
= 1;
1226 if (!inout
&& part
) {
1228 p
->c_end
= ct
->c_end
;
1230 if (p
->c_begin
>= p
->c_end
) {
1231 for (next
= &m
->mp_parts
; *next
!= part
;
1232 next
= &((*next
)->mp_next
))
1241 /* reverse the order of the parts for multipart/alternative */
1242 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1248 * label all subparts with part number, and
1249 * then initialize the content of the subpart.
1254 char partnam
[BUFSIZ
];
1257 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1258 pp
= partnam
+ strlen (partnam
);
1263 for (part
= m
->mp_parts
, partnum
= 1; part
;
1264 part
= part
->mp_next
, partnum
++) {
1267 sprintf (pp
, "%d", partnum
);
1268 p
->c_partno
= mh_xstrdup(partnam
);
1270 /* initialize the content of the subparts */
1271 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1280 get_leftover_mp_content (ct
, 1);
1281 get_leftover_mp_content (ct
, 0);
1291 * reverse the order of the parts of a multipart/alternative,
1292 * presumably to put the "most favored" alternative first, for
1293 * ease of choosing/displaying it later on. from a mail message on
1294 * nmh-workers, from kenh:
1295 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1296 * see code in mhn that did the same thing... Acccording to the RCS
1297 * logs, that code was around from the initial checkin of mhn.c by
1298 * John Romine in 1992, which is as far back as we have."
1301 reverse_parts (CT ct
)
1303 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1307 /* Reverse the order of its parts by walking the mp_parts list
1308 and pushing each node to the front. */
1309 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1310 next
= part
->mp_next
;
1311 part
->mp_next
= m
->mp_parts
;
1317 move_preferred_part (CT ct
, char *type
, char *subtype
)
1319 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1320 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1324 /* move the matching part(s) to the head of the list: walk the
1325 * list of parts, move matching parts to a new list (maintaining
1326 * their order), and finally, concatenate the old list onto the
1333 head
->mp_next
= m
->mp_parts
;
1334 nhead
->mp_next
= NULL
;
1338 part
= head
->mp_next
;
1339 while (part
!= NULL
) {
1340 ci
= &part
->mp_part
->c_ctinfo
;
1341 if (!strcasecmp(ci
->ci_type
, type
) &&
1342 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1343 prev
->mp_next
= part
->mp_next
;
1344 part
->mp_next
= NULL
;
1345 ntail
->mp_next
= part
;
1347 part
= prev
->mp_next
;
1350 part
= prev
->mp_next
;
1353 ntail
->mp_next
= head
->mp_next
;
1354 m
->mp_parts
= nhead
->mp_next
;
1359 * move parts that match the user's preferences (-prefer) to the head
1360 * of the line. process preferences in reverse so first one given
1361 * ends up first in line
1367 for (i
= npreferred
-1; i
>= 0; i
--)
1368 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1373 /* parse_mime() arranges alternates in reverse (priority) order. This
1374 function can be used to reverse them back. This will put, for
1375 example, a text/plain part before a text/html part in a
1376 multipart/alternative part, for example, where it belongs. */
1378 reverse_alternative_parts (CT ct
) {
1379 if (ct
->c_type
== CT_MULTIPART
) {
1380 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1383 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1387 /* And call recursively on each part of a multipart. */
1388 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1389 reverse_alternative_parts (part
->mp_part
);
1402 CI ci
= &ct
->c_ctinfo
;
1404 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1406 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1407 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1411 /* check for missing subtype */
1412 if (!*ci
->ci_subtype
)
1413 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1416 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1418 switch (ct
->c_subtype
) {
1419 case MESSAGE_RFC822
:
1422 case MESSAGE_PARTIAL
:
1428 ct
->c_ctparams
= (void *) p
;
1430 /* scan for parameters "id", "number", and "total" */
1431 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1432 if (!strcasecmp (pm
->pm_name
, "id")) {
1433 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1436 if (!strcasecmp (pm
->pm_name
, "number")) {
1437 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1438 || p
->pm_partno
< 1) {
1441 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1442 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1443 ct
->c_file
, TYPE_FIELD
);
1448 if (!strcasecmp (pm
->pm_name
, "total")) {
1449 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1458 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1460 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1461 ci
->ci_type
, ci
->ci_subtype
,
1462 ct
->c_file
, TYPE_FIELD
);
1468 case MESSAGE_EXTERNAL
:
1476 ct
->c_ctparams
= (void *) e
;
1479 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1480 advise (ct
->c_file
, "unable to open for reading");
1484 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1486 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1494 p
->c_ceopenfnx
= NULL
;
1495 if ((exresult
= params_external (ct
, 0)) != NOTOK
1496 && p
->c_ceopenfnx
== openMail
) {
1500 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1502 content_error (NULL
, ct
,
1503 "empty body for access-type=mail-server");
1507 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1508 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1510 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1512 adios ("failed", "fread");
1515 adios (NULL
, "unexpected EOF from fread");
1518 bp
+= cc
, size
-= cc
;
1525 p
->c_end
= p
->c_begin
;
1530 if (exresult
== NOTOK
)
1532 if (e
->eb_flags
== NOTOK
)
1535 switch (p
->c_type
) {
1540 if (p
->c_subtype
!= MESSAGE_RFC822
)
1544 e
->eb_partno
= ct
->c_partno
;
1546 (*p
->c_ctinitfnx
) (p
);
1561 params_external (CT ct
, int composing
)
1564 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1565 CI ci
= &ct
->c_ctinfo
;
1567 ct
->c_ceopenfnx
= NULL
;
1568 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1569 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1570 struct str2init
*s2i
;
1571 CT p
= e
->eb_content
;
1573 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1574 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1577 e
->eb_access
= pm
->pm_value
;
1578 e
->eb_flags
= NOTOK
;
1579 p
->c_encoding
= CE_EXTERNAL
;
1582 e
->eb_access
= s2i
->si_key
;
1583 e
->eb_flags
= s2i
->si_val
;
1584 p
->c_encoding
= CE_EXTERNAL
;
1586 /* Call the Init function for this external type */
1587 if ((*s2i
->si_init
)(p
) == NOTOK
)
1591 if (!strcasecmp (pm
->pm_name
, "name")) {
1592 e
->eb_name
= pm
->pm_value
;
1595 if (!strcasecmp (pm
->pm_name
, "permission")) {
1596 e
->eb_permission
= pm
->pm_value
;
1599 if (!strcasecmp (pm
->pm_name
, "site")) {
1600 e
->eb_site
= pm
->pm_value
;
1603 if (!strcasecmp (pm
->pm_name
, "directory")) {
1604 e
->eb_dir
= pm
->pm_value
;
1607 if (!strcasecmp (pm
->pm_name
, "mode")) {
1608 e
->eb_mode
= pm
->pm_value
;
1611 if (!strcasecmp (pm
->pm_name
, "size")) {
1612 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1615 if (!strcasecmp (pm
->pm_name
, "server")) {
1616 e
->eb_server
= pm
->pm_value
;
1619 if (!strcasecmp (pm
->pm_name
, "subject")) {
1620 e
->eb_subject
= pm
->pm_value
;
1623 if (!strcasecmp (pm
->pm_name
, "url")) {
1625 * According to RFC 2017, we have to remove all whitespace from
1629 char *u
, *p
= pm
->pm_value
;
1630 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1632 for (; *p
!= '\0'; p
++) {
1633 if (! isspace((unsigned char) *p
))
1640 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1641 e
->eb_body
= getcpy (pm
->pm_value
);
1646 if (!e
->eb_access
) {
1648 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1649 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1662 InitApplication (CT ct
)
1664 CI ci
= &ct
->c_ctinfo
;
1667 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1674 * TRANSFER ENCODINGS
1678 init_encoding (CT ct
, OpenCEFunc openfnx
)
1680 ct
->c_ceopenfnx
= openfnx
;
1681 ct
->c_ceclosefnx
= close_encoding
;
1682 ct
->c_cesizefnx
= size_encoding
;
1689 close_encoding (CT ct
)
1691 CE ce
= &ct
->c_cefile
;
1700 static unsigned long
1701 size_encoding (CT ct
)
1706 CE ce
= &ct
->c_cefile
;
1709 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1710 return (long) st
.st_size
;
1713 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1714 return (long) st
.st_size
;
1718 if (ct
->c_encoding
== CE_EXTERNAL
)
1719 return (ct
->c_end
- ct
->c_begin
);
1722 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1723 return (ct
->c_end
- ct
->c_begin
);
1725 if (fstat (fd
, &st
) != NOTOK
)
1726 size
= (long) st
.st_size
;
1730 (*ct
->c_ceclosefnx
) (ct
);
1742 return init_encoding (ct
, openBase64
);
1747 openBase64 (CT ct
, char **file
)
1750 int fd
, own_ct_fp
= 0;
1751 char *cp
, *buffer
= NULL
;
1752 /* sbeck -- handle suffixes */
1754 CE ce
= &ct
->c_cefile
;
1755 unsigned char *decoded
;
1757 unsigned char digest
[16];
1760 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1765 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1766 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1772 if (*file
== NULL
) {
1775 ce
->ce_file
= mh_xstrdup(*file
);
1779 /* sbeck@cise.ufl.edu -- handle suffixes */
1781 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1782 if (ce
->ce_unlink
) {
1783 /* Create temporary file with filename extension. */
1784 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1785 adios(NULL
, "unable to create temporary file in %s",
1789 ce
->ce_file
= add (cp
, ce
->ce_file
);
1791 } else if (*file
== NULL
) {
1793 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1794 adios(NULL
, "unable to create temporary file in %s",
1797 ce
->ce_file
= mh_xstrdup(tempfile
);
1800 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1801 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1805 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1806 adios (NULL
, "internal error(1)");
1808 buffer
= mh_xmalloc (len
+ 1);
1811 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1812 content_error (ct
->c_file
, ct
, "unable to open for reading");
1818 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1821 switch (cc
= read (fd
, cp
, len
)) {
1823 content_error (ct
->c_file
, ct
, "error reading from");
1827 content_error (NULL
, ct
, "premature eof");
1838 /* decodeBase64() requires null-terminated input. */
1841 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1842 ct
->c_digested
? digest
: NULL
) == OK
) {
1844 unsigned char *decoded_p
= decoded
;
1845 for (i
= 0; i
< decoded_len
; ++i
) {
1846 putc (*decoded_p
++, ce
->ce_fp
);
1849 if (ferror (ce
->ce_fp
)) {
1850 content_error (ce
->ce_file
, ct
, "error writing to");
1854 if (ct
->c_digested
) {
1855 if (memcmp(digest
, ct
->c_digest
,
1856 sizeof(digest
) / sizeof(digest
[0]))) {
1857 content_error (NULL
, ct
,
1858 "content integrity suspect (digest mismatch) -- continuing");
1861 fprintf (stderr
, "content integrity confirmed\n");
1869 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1871 if (fflush (ce
->ce_fp
)) {
1872 content_error (ce
->ce_file
, ct
, "error writing to");
1876 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1879 *file
= ce
->ce_file
;
1885 return fileno (ce
->ce_fp
);
1892 free_encoding (ct
, 0);
1902 static char hex2nib
[0x80] = {
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1909 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1910 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1911 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1913 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1914 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1915 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1916 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1917 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1918 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1925 return init_encoding (ct
, openQuoted
);
1930 openQuoted (CT ct
, char **file
)
1932 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1938 CE ce
= &ct
->c_cefile
;
1939 /* sbeck -- handle suffixes */
1944 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1949 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1950 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1956 if (*file
== NULL
) {
1959 ce
->ce_file
= mh_xstrdup(*file
);
1963 /* sbeck@cise.ufl.edu -- handle suffixes */
1965 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1966 if (ce
->ce_unlink
) {
1967 /* Create temporary file with filename extension. */
1968 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1969 adios(NULL
, "unable to create temporary file in %s",
1973 ce
->ce_file
= add (cp
, ce
->ce_file
);
1975 } else if (*file
== NULL
) {
1977 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1978 adios(NULL
, "unable to create temporary file in %s",
1981 ce
->ce_file
= mh_xstrdup(tempfile
);
1984 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1985 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1989 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1990 adios (NULL
, "internal error(2)");
1993 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1994 content_error (ct
->c_file
, ct
, "unable to open for reading");
2000 if ((digested
= ct
->c_digested
))
2001 MD5Init (&mdContext
);
2008 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2010 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2011 content_error (NULL
, ct
, "premature eof");
2015 if ((cc
= gotlen
) > len
)
2019 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2020 if (!isspace ((unsigned char) *ep
))
2024 for (; cp
< ep
; cp
++) {
2026 /* in an escape sequence */
2028 /* at byte 1 of an escape sequence */
2029 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2030 /* next is byte 2 */
2033 /* at byte 2 of an escape sequence */
2035 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2036 putc (mask
, ce
->ce_fp
);
2038 MD5Update (&mdContext
, &mask
, 1);
2039 if (ferror (ce
->ce_fp
)) {
2040 content_error (ce
->ce_file
, ct
, "error writing to");
2043 /* finished escape sequence; next may be literal or a new
2044 * escape sequence */
2047 /* on to next byte */
2051 /* not in an escape sequence */
2053 /* starting an escape sequence, or invalid '='? */
2054 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2055 /* "=\n" soft line break, eat the \n */
2059 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2060 /* We don't have 2 bytes left, so this is an invalid
2061 * escape sequence; just show the raw bytes (below). */
2062 } else if (isxdigit ((unsigned char) cp
[1]) &&
2063 isxdigit ((unsigned char) cp
[2])) {
2064 /* Next 2 bytes are hex digits, making this a valid escape
2065 * sequence; let's decode it (above). */
2069 /* One or both of the next 2 is out of range, making this
2070 * an invalid escape sequence; just show the raw bytes
2075 /* Just show the raw byte. */
2076 putc (*cp
, ce
->ce_fp
);
2079 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2081 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2084 if (ferror (ce
->ce_fp
)) {
2085 content_error (ce
->ce_file
, ct
, "error writing to");
2091 content_error (NULL
, ct
,
2092 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2096 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2098 if (fflush (ce
->ce_fp
)) {
2099 content_error (ce
->ce_file
, ct
, "error writing to");
2104 unsigned char digest
[16];
2106 MD5Final (digest
, &mdContext
);
2107 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2108 sizeof(digest
) / sizeof(digest
[0])))
2109 content_error (NULL
, ct
,
2110 "content integrity suspect (digest mismatch) -- continuing");
2113 fprintf (stderr
, "content integrity confirmed\n");
2116 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2119 *file
= ce
->ce_file
;
2125 return fileno (ce
->ce_fp
);
2128 free_encoding (ct
, 0);
2145 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2148 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2154 open7Bit (CT ct
, char **file
)
2156 int cc
, fd
, len
, own_ct_fp
= 0;
2157 char buffer
[BUFSIZ
];
2158 /* sbeck -- handle suffixes */
2161 CE ce
= &ct
->c_cefile
;
2164 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2169 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2170 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2176 if (*file
== NULL
) {
2179 ce
->ce_file
= mh_xstrdup(*file
);
2183 /* sbeck@cise.ufl.edu -- handle suffixes */
2185 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2186 if (ce
->ce_unlink
) {
2187 /* Create temporary file with filename extension. */
2188 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2189 adios(NULL
, "unable to create temporary file in %s",
2193 ce
->ce_file
= add (cp
, ce
->ce_file
);
2195 } else if (*file
== NULL
) {
2197 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2198 adios(NULL
, "unable to create temporary file in %s",
2201 ce
->ce_file
= mh_xstrdup(tempfile
);
2204 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2205 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2209 if (ct
->c_type
== CT_MULTIPART
) {
2210 CI ci
= &ct
->c_ctinfo
;
2214 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2215 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2216 + 1 + strlen (ci
->ci_subtype
);
2217 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2220 fputs (buffer
, ce
->ce_fp
);
2224 if (ci
->ci_comment
) {
2225 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2226 fputs ("\n\t", ce
->ce_fp
);
2230 putc (' ', ce
->ce_fp
);
2233 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2236 fprintf (ce
->ce_fp
, "\n");
2238 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2240 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2242 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2243 fprintf (ce
->ce_fp
, "\n");
2246 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2247 adios (NULL
, "internal error(3)");
2250 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2251 content_error (ct
->c_file
, ct
, "unable to open for reading");
2257 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2259 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2261 content_error (ct
->c_file
, ct
, "error reading from");
2265 content_error (NULL
, ct
, "premature eof");
2273 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2274 advise ("open7Bit", "fwrite");
2276 if (ferror (ce
->ce_fp
)) {
2277 content_error (ce
->ce_file
, ct
, "error writing to");
2282 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2284 if (fflush (ce
->ce_fp
)) {
2285 content_error (ce
->ce_file
, ct
, "error writing to");
2289 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2292 *file
= ce
->ce_file
;
2297 return fileno (ce
->ce_fp
);
2300 free_encoding (ct
, 0);
2314 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2316 char cachefile
[BUFSIZ
];
2319 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2324 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2325 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2331 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2332 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2333 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2334 ce
->ce_file
= mh_xstrdup(cachefile
);
2338 admonish (cachefile
, "unable to fopen for reading");
2341 *fd
= fileno (ce
->ce_fp
);
2345 *file
= ce
->ce_file
;
2346 *fd
= fileno (ce
->ce_fp
);
2357 return init_encoding (ct
, openFile
);
2362 openFile (CT ct
, char **file
)
2365 char cachefile
[BUFSIZ
];
2366 struct exbody
*e
= ct
->c_ctexbody
;
2367 CE ce
= &ct
->c_cefile
;
2369 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2381 content_error (NULL
, ct
, "missing name parameter");
2385 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2388 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2389 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2393 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2394 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2395 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2399 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2400 if ((fp
= fopen (cachefile
, "w"))) {
2402 char buffer
[BUFSIZ
];
2403 FILE *gp
= ce
->ce_fp
;
2405 fseek (gp
, 0L, SEEK_SET
);
2407 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2409 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2410 advise ("openFile", "fwrite");
2415 admonish (ce
->ce_file
, "error reading");
2416 (void) m_unlink (cachefile
);
2420 admonish (cachefile
, "error writing");
2421 (void) m_unlink (cachefile
);
2428 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2429 *file
= ce
->ce_file
;
2430 return fileno (ce
->ce_fp
);
2440 return init_encoding (ct
, openFTP
);
2445 openFTP (CT ct
, char **file
)
2447 int cachetype
, caching
, fd
;
2449 char *bp
, *ftp
, *user
, *pass
;
2450 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2452 CE ce
= &ct
->c_cefile
;
2453 static char *username
= NULL
;
2454 static char *password
= NULL
;
2458 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2464 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2475 if (!e
->eb_name
|| !e
->eb_site
) {
2476 content_error (NULL
, ct
, "missing %s parameter",
2477 e
->eb_name
? "site": "name");
2481 /* Get the buffer ready to go */
2483 buflen
= sizeof(buffer
);
2486 * Construct the query message for user
2488 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2494 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2500 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2501 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2506 if (e
->eb_size
> 0) {
2507 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2512 snprintf (bp
, buflen
, "? ");
2515 * Now, check the answer
2517 if (!read_yes_or_no_if_tty (buffer
))
2522 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2526 ruserpass (e
->eb_site
, &username
, &password
, 0);
2531 ce
->ce_unlink
= (*file
== NULL
);
2533 cachefile
[0] = '\0';
2534 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2535 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2536 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2537 if (*file
== NULL
) {
2544 ce
->ce_file
= mh_xstrdup(*file
);
2546 ce
->ce_file
= mh_xstrdup(cachefile
);
2549 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2550 adios(NULL
, "unable to create temporary file in %s",
2553 ce
->ce_file
= mh_xstrdup(tempfile
);
2556 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2557 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2562 int child_id
, i
, vecp
;
2566 vec
[vecp
++] = r1bindex (ftp
, '/');
2567 vec
[vecp
++] = e
->eb_site
;
2570 vec
[vecp
++] = e
->eb_dir
;
2571 vec
[vecp
++] = e
->eb_name
;
2572 vec
[vecp
++] = ce
->ce_file
,
2573 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2574 ? "ascii" : "binary";
2579 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2583 adios ("fork", "unable to");
2587 close (fileno (ce
->ce_fp
));
2589 fprintf (stderr
, "unable to exec ");
2595 if (pidXwait (child_id
, NULL
)) {
2596 username
= password
= NULL
;
2606 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2611 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2612 if ((fp
= fopen (cachefile
, "w"))) {
2614 FILE *gp
= ce
->ce_fp
;
2616 fseek (gp
, 0L, SEEK_SET
);
2618 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2620 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2621 advise ("openFTP", "fwrite");
2626 admonish (ce
->ce_file
, "error reading");
2627 (void) m_unlink (cachefile
);
2631 admonish (cachefile
, "error writing");
2632 (void) m_unlink (cachefile
);
2640 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2641 *file
= ce
->ce_file
;
2642 return fileno (ce
->ce_fp
);
2653 return init_encoding (ct
, openMail
);
2658 openMail (CT ct
, char **file
)
2660 int child_id
, fd
, i
, vecp
;
2662 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2663 struct exbody
*e
= ct
->c_ctexbody
;
2664 CE ce
= &ct
->c_cefile
;
2666 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2677 if (!e
->eb_server
) {
2678 content_error (NULL
, ct
, "missing server parameter");
2682 /* Get buffer ready to go */
2684 buflen
= sizeof(buffer
);
2686 /* Now, construct query message */
2687 snprintf (bp
, buflen
, "Retrieve content");
2693 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2699 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2701 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2703 /* Now, check answer */
2704 if (!read_yes_or_no_if_tty (buffer
))
2708 vec
[vecp
++] = r1bindex (mailproc
, '/');
2709 vec
[vecp
++] = e
->eb_server
;
2710 vec
[vecp
++] = "-subject";
2711 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2712 vec
[vecp
++] = "-body";
2713 vec
[vecp
++] = e
->eb_body
;
2716 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2720 advise ("fork", "unable to");
2724 execvp (mailproc
, vec
);
2725 fprintf (stderr
, "unable to exec ");
2731 if (pidXwait (child_id
, NULL
) == OK
)
2732 advise (NULL
, "request sent");
2736 if (*file
== NULL
) {
2738 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2739 adios(NULL
, "unable to create temporary file in %s",
2742 ce
->ce_file
= mh_xstrdup(tempfile
);
2745 ce
->ce_file
= mh_xstrdup(*file
);
2749 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2750 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2754 /* showproc is for mhshow and mhstore, though mhlist -debug
2755 * prints it, too. */
2756 mh_xfree(ct
->c_showproc
);
2757 ct
->c_showproc
= mh_xstrdup("true");
2759 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2760 *file
= ce
->ce_file
;
2761 return fileno (ce
->ce_fp
);
2772 return init_encoding (ct
, openURL
);
2777 openURL (CT ct
, char **file
)
2779 struct exbody
*e
= ct
->c_ctexbody
;
2780 CE ce
= &ct
->c_cefile
;
2781 char *urlprog
, *program
;
2782 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2783 int fd
, caching
, cachetype
;
2784 struct msgs_array args
= { 0, 0, NULL
};
2787 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2791 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2795 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2807 content_error(NULL
, ct
, "missing url parameter");
2811 ce
->ce_unlink
= (*file
== NULL
);
2813 cachefile
[0] = '\0';
2815 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2816 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2817 if (*file
== NULL
) {
2824 ce
->ce_file
= mh_xstrdup(*file
);
2826 ce
->ce_file
= mh_xstrdup(cachefile
);
2829 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2830 adios(NULL
, "unable to create temporary file in %s",
2833 ce
->ce_file
= mh_xstrdup(tempfile
);
2836 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2837 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2841 switch (child_id
= fork()) {
2843 adios ("fork", "unable to");
2847 argsplit_msgarg(&args
, urlprog
, &program
);
2848 app_msgarg(&args
, e
->eb_url
);
2849 app_msgarg(&args
, NULL
);
2850 dup2(fileno(ce
->ce_fp
), 1);
2851 close(fileno(ce
->ce_fp
));
2852 execvp(program
, args
.msgs
);
2853 fprintf(stderr
, "Unable to exec ");
2859 if (pidXwait(child_id
, NULL
)) {
2867 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2872 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2873 if ((fp
= fopen(cachefile
, "w"))) {
2875 FILE *gp
= ce
->ce_fp
;
2877 fseeko(gp
, 0, SEEK_SET
);
2879 while ((cc
= fread(buffer
, sizeof(*buffer
),
2880 sizeof(buffer
), gp
)) > 0)
2881 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2882 advise ("openURL", "fwrite");
2888 admonish(ce
->ce_file
, "error reading");
2889 (void) m_unlink (cachefile
);
2896 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2897 *file
= ce
->ce_file
;
2903 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2904 * has to be base64 decoded.
2907 readDigest (CT ct
, char *cp
)
2909 unsigned char *digest
;
2912 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2913 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2915 if (strlen ((char *) digest
) <= maxlen
) {
2916 memcpy (ct
->c_digest
, digest
, maxlen
);
2921 fprintf (stderr
, "MD5 digest=");
2922 for (i
= 0; i
< maxlen
; ++i
) {
2923 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2925 fprintf (stderr
, "\n");
2931 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2932 (int) strlen ((char *) digest
));
2942 /* Multipart parts might have content before the first subpart and/or
2943 after the last subpart that hasn't been stored anywhere else, so do
2946 get_leftover_mp_content (CT ct
, int before
/* or after */)
2948 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2950 int found_boundary
= 0;
2956 char *content
= NULL
;
2958 if (! m
) return NOTOK
;
2961 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2963 /* Isolate the beginning of this part to the beginning of the
2964 first subpart and save any content between them. */
2965 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2966 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2967 boundary
= concat ("--", m
->mp_start
, NULL
);
2969 struct part
*last_subpart
= NULL
;
2970 struct part
*subpart
;
2972 /* Go to the last subpart to get its end position. */
2973 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2974 last_subpart
= subpart
;
2977 if (last_subpart
== NULL
) return NOTOK
;
2979 /* Isolate the end of the last subpart to the end of this part
2980 and save any content between them. */
2981 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2982 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2983 boundary
= concat ("--", m
->mp_stop
, NULL
);
2986 /* Back up by 1 to pick up the newline. */
2987 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2989 /* Don't look beyond beginning of first subpart (before) or
2990 next part (after). */
2991 if (read
> max
) bufp
[read
-max
] = '\0';
2994 if (! strcmp (bufp
, boundary
)) {
2998 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3004 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3006 char *old_content
= content
;
3007 content
= concat (content
, bufp
, NULL
);
3011 ? concat ("\n", bufp
, NULL
)
3012 : concat (bufp
, NULL
);
3017 if (found_boundary
|| read
> max
) break;
3019 if (read
> max
) break;
3023 /* Skip the newline if that's all there is. */
3027 /* Remove trailing newline, except at EOF. */
3028 if ((before
|| ! feof (ct
->c_fp
)) &&
3029 (cp
= content
+ strlen (content
)) > content
&&
3034 if (strlen (content
) > 1) {
3036 m
->mp_content_before
= content
;
3038 m
->mp_content_after
= content
;
3053 ct_type_str (int type
) {
3055 case CT_APPLICATION
:
3056 return "application";
3072 return "unknown_type";
3078 ct_subtype_str (int type
, int subtype
) {
3080 case CT_APPLICATION
:
3082 case APPLICATION_OCTETS
:
3084 case APPLICATION_POSTSCRIPT
:
3085 return "postscript";
3087 return "unknown_app_subtype";
3091 case MESSAGE_RFC822
:
3093 case MESSAGE_PARTIAL
:
3095 case MESSAGE_EXTERNAL
:
3098 return "unknown_msg_subtype";
3104 case MULTI_ALTERNATE
:
3105 return "alternative";
3108 case MULTI_PARALLEL
:
3113 return "unknown_multipart_subtype";
3124 return "unknown_text_subtype";
3127 return "unknown_type";
3133 ct_str_type (const char *type
) {
3134 struct str2init
*s2i
;
3136 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3137 if (! strcasecmp (type
, s2i
->si_key
)) {
3141 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3150 ct_str_subtype (int type
, const char *subtype
) {
3154 case CT_APPLICATION
:
3155 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3156 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3160 return kv
->kv_value
;
3162 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3163 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3167 return kv
->kv_value
;
3169 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3170 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3174 return kv
->kv_value
;
3176 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3177 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3181 return kv
->kv_value
;
3188 /* Find the content type and InitFunc for the CT. */
3189 const struct str2init
*
3190 get_ct_init (int type
) {
3191 const struct str2init
*sp
;
3193 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3194 if (type
== sp
->si_val
) {
3203 ce_str (int encoding
) {
3208 return "quoted-printable";
3224 /* Find the content type and InitFunc for the content encoding method. */
3225 const struct str2init
*
3226 get_ce_method (const char *method
) {
3227 struct str2init
*sp
;
3229 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3230 if (! strcasecmp (method
, sp
->si_key
)) {
3239 * Parse a series of MIME attributes (or parameters) given a header as
3242 * Arguments include:
3244 * filename - Name of input file (for error messages)
3245 * fieldname - Name of field being processed
3246 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3247 * Updated to point to end of attributes when finished.
3248 * param_head - Pointer to head of parameter list
3249 * param_tail - Pointer to tail of parameter list
3250 * commentp - Pointer to header comment pointer (may be NULL)
3252 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3253 * DONE to indicate a benign error (minor parsing error, but the program
3258 parse_header_attrs (const char *filename
, const char *fieldname
,
3259 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3262 char *cp
= *header_attrp
;
3268 struct sectlist
*next
;
3274 struct sectlist
*sechead
;
3275 struct parmlist
*next
;
3276 } *pp
, *pp2
, *phead
= NULL
;
3278 while (*cp
== ';') {
3279 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3280 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3283 while (isspace ((unsigned char) *cp
))
3287 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3292 if (! suppress_extraneous_trailing_semicolon_warning
) {
3294 "extraneous trailing ';' in message %s's %s: "
3296 filename
, fieldname
);
3301 /* down case the attribute name */
3302 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3303 *dp
= tolower ((unsigned char) *dp
);
3305 for (up
= dp
; isspace ((unsigned char) *dp
);)
3307 if (dp
== cp
|| *dp
!= '=') {
3309 "invalid parameter in message %s's %s: "
3310 "field\n%*sparameter %s (error detected at offset %d)",
3311 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3316 * To handle RFC 2231, we have to deal with the following extensions:
3318 * name*=encoded-value
3319 * name*<N>=part-N-of-a-parameter-value
3320 * name*<N>*=encoded-part-N-of-a-parameter-value
3323 * If there's a * right before the equal sign, it's encoded.
3324 * If there's a * and one or more digits, then it's section N.
3326 * Remember we can have one or the other, or both. cp points to
3327 * beginning of name, up points past the last character in the
3331 for (vp
= cp
; vp
< up
; vp
++) {
3332 if (*vp
== '*' && vp
< up
- 1) {
3335 } else if (*vp
== '*' && vp
== up
- 1) {
3337 } else if (partial
) {
3338 if (isdigit((unsigned char) *vp
))
3339 index
= *vp
- '0' + index
* 10;
3341 advise (NULL
, "invalid parameter index in message %s's "
3342 "%s: field\n%*s(parameter %s)", filename
,
3343 fieldname
, strlen(invo_name
) + 2, "", cp
);
3352 * Break out the parameter name and value sections and allocate
3356 nameptr
= mh_xmalloc(len
+ 1);
3357 strncpy(nameptr
, cp
, len
);
3358 nameptr
[len
] = '\0';
3360 for (dp
++; isspace ((unsigned char) *dp
);)
3365 * Single quotes delimit the character set and language tag.
3366 * They are required on the first section (or a complete
3371 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3377 charset
= mh_xmalloc(len
+ 1);
3378 strncpy(charset
, dp
, len
);
3379 charset
[len
] = '\0';
3385 advise(NULL
, "missing charset in message %s's %s: "
3386 "field\n%*s(parameter %s)", filename
, fieldname
,
3387 strlen(invo_name
) + 2, "", nameptr
);
3393 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3400 lang
= mh_xmalloc(len
+ 1);
3401 strncpy(lang
, dp
, len
);
3408 advise(NULL
, "missing language tag in message %s's %s: "
3409 "field\n%*s(parameter %s)", filename
, fieldname
,
3410 strlen(invo_name
) + 2, "", nameptr
);
3420 * At this point vp should be pointing at the beginning
3421 * of the encoded value/section. Continue until we reach
3422 * the end or get whitespace. But first, calculate the
3423 * length so we can allocate the correct buffer size.
3426 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3428 if (*(vp
+ 1) == '\0' ||
3429 !isxdigit((unsigned char) *(vp
+ 1)) ||
3430 *(vp
+ 2) == '\0' ||
3431 !isxdigit((unsigned char) *(vp
+ 2))) {
3432 advise(NULL
, "invalid encoded sequence in message "
3433 "%s's %s: field\n%*s(parameter %s)",
3434 filename
, fieldname
, strlen(invo_name
) + 2,
3446 up
= valptr
= mh_xmalloc(len
+ 1);
3448 for (vp
= dp
; istoken(*vp
); vp
++) {
3450 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3461 * A "normal" string. If it's got a leading quote, then we
3462 * strip the quotes out. Otherwise go until we reach the end
3463 * or get whitespace. Note we scan it twice; once to get the
3464 * length, then the second time copies it into the destination
3471 for (cp
= dp
+ 1;;) {
3476 "invalid quoted-string in message %s's %s: "
3477 "field\n%*s(parameter %s)",
3478 filename
, fieldname
, strlen(invo_name
) + 2, "",
3499 for (cp
= dp
; istoken (*cp
); cp
++) {
3504 valptr
= mh_xmalloc(len
+ 1);
3508 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3516 strncpy(valptr
, cp
= dp
, len
);
3524 * If 'partial' is set, we don't allocate a parameter now. We
3525 * put it on the parameter linked list to be reassembled later.
3527 * "phead" points to a list of all parameters we need to reassemble.
3528 * Each parameter has a list of sections. We insert the sections in
3533 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3534 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3549 * Insert this into the section linked list
3557 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3558 sp
->next
= pp
->sechead
;
3561 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3562 if (sp2
->index
== sp
->index
) {
3563 advise (NULL
, "duplicate index (%d) in message "
3564 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3565 filename
, fieldname
, strlen(invo_name
) + 2, "",
3569 if (sp2
->index
< sp
->index
&&
3570 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3571 sp
->next
= sp2
->next
;
3578 advise(NULL
, "Internal error: cannot insert partial "
3579 "param in message %s's %s: field\n%*s(parameter %s)",
3580 filename
, fieldname
, strlen(invo_name
) + 2, "",
3587 * Save our charset and lang tags.
3590 if (index
== 0 && encoded
) {
3591 mh_xfree(pp
->charset
);
3592 pp
->charset
= charset
;
3597 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3598 pm
->pm_charset
= charset
;
3602 while (isspace ((unsigned char) *cp
))
3606 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3612 * Now that we're done, reassemble all of the partial parameters.
3615 for (pp
= phead
; pp
!= NULL
; ) {
3619 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3620 if (sp
->index
!= pindex
++) {
3621 advise(NULL
, "missing section %d for parameter in "
3622 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3623 filename
, fieldname
, strlen(invo_name
) + 2, "",
3630 p
= q
= mh_xmalloc(tlen
+ 1);
3631 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3632 memcpy(q
, sp
->value
, sp
->len
);
3642 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3643 pm
->pm_charset
= pp
->charset
;
3644 pm
->pm_lang
= pp
->lang
;
3655 * Return the charset for a particular content type.
3659 content_charset (CT ct
) {
3660 char *ret_charset
= NULL
;
3662 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3664 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3669 * Create a string based on a list of output parameters. Assume that this
3670 * parameter string will be appended to an existing header, so start out
3671 * with the separator (;). Perform RFC 2231 encoding when necessary.
3675 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3677 char *paramout
= NULL
;
3678 char line
[CPERLIN
* 2], *q
;
3679 int curlen
, index
, cont
, encode
, i
;
3680 size_t valoff
, numchars
;
3682 while (params
!= NULL
) {
3688 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3691 if (strlen(params
->pm_name
) > CPERLIN
) {
3692 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3697 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3700 * Loop until we get a parameter that fits within a line. We
3701 * assume new lines start with a tab, so check our overflow based
3711 * At this point we're definitely continuing the line, so
3712 * be sure to include the parameter name and section index.
3715 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3716 params
->pm_name
, index
);
3719 * Both of these functions do a NUL termination
3723 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3724 numchars
, valoff
, index
);
3726 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3736 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3741 * "line" starts with a ;\n\t, so that doesn't count against
3742 * the length. But add 8 since it starts with a tab; that's
3743 * how we end up with 5.
3746 initialwidth
= strlen(line
) + 5;
3749 * At this point the line should be built, so add it to our
3750 * current output buffer.
3753 paramout
= add(line
, paramout
);
3757 * If this won't fit on the line, start a new one. Save room in
3758 * case we need a semicolon on the end
3761 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3773 * At this point, we're either finishing a contined parameter, or
3774 * we're working on a new one.
3778 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3779 params
->pm_name
, index
);
3781 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3786 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3787 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3789 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3790 strlen(params
->pm_value
+ valoff
), valoff
);
3797 paramout
= add(line
, paramout
);
3798 initialwidth
+= strlen(line
);
3800 params
= params
->pm_next
;
3804 *offsetout
= initialwidth
;
3810 * Calculate the size of a parameter.
3814 * pm - The parameter being output
3815 * index - If continuing the parameter, the index of the section
3817 * valueoff - The current offset into the parameter value that we're
3818 * working on (previous sections have consumed valueoff bytes).
3819 * encode - Set if we should perform encoding on this parameter section
3820 * (given that we're consuming bytesfit bytes).
3821 * cont - Set if the remaining data in value will not fit on a single
3822 * line and will need to be continued.
3823 * bytesfit - The number of bytes that we can consume from the parameter
3824 * value and still fit on a completely new line. The
3825 * calculation assumes the new line starts with a tab,
3826 * includes the parameter name and any encoding, and fits
3827 * within CPERLIN bytes. Will always be at least 1.
3831 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3834 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3835 size_t len
= 0, fit
= 0;
3836 int fitlimit
= 0, eightbit
, maxfit
;
3841 * Add up the length. First, start with the parameter name.
3844 len
= strlen(pm
->pm_name
);
3847 * Scan the parameter value and see if we need to do encoding for this
3851 eightbit
= contains8bit(start
, NULL
);
3854 * Determine if we need to encode this section. Encoding is necessary if:
3856 * - There are any 8-bit characters at all and we're on the first
3858 * - There are 8-bit characters within N bytes of our section start.
3859 * N is calculated based on the number of bytes it would take to
3860 * reach CPERLIN. Specifically:
3861 * 8 (starting tab) +
3862 * strlen(param name) +
3863 * 4 ('* for section marker, '=', opening/closing '"')
3865 * is the number of bytes used by everything that isn't part of the
3866 * value. So that gets subtracted from CPERLIN.
3869 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3870 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3871 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3875 len
++; /* Add in equal sign */
3879 * We're using maxfit as a marker for how many characters we can
3880 * fit into the line. Bump it by two because we're not using quotes
3887 * If we don't have a charset or language tag in this parameter,
3891 if (! pm
->pm_charset
) {
3892 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3893 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3894 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3895 "local character set is US-ASCII", pm
->pm_name
);
3898 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3900 len
++; /* For the encoding marker */
3903 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3908 * We know we definitely need to include an index. maxfit already
3909 * includes the section marker.
3911 len
+= strlen(indexchar
);
3913 for (p
= start
; *p
!= '\0'; p
++) {
3914 if (isparamencode(*p
)) {
3922 * Just so there's no confusion: maxfit is counting OUTPUT
3923 * characters (post-encoding). fit is counting INPUT characters.
3925 if (! fitlimit
&& maxfit
>= 0)
3927 else if (! fitlimit
)
3932 * Calculate the string length, but add room for quoting \
3933 * and " if necessary. Also account for quotes at beginning
3936 for (p
= start
; *p
!= '\0'; p
++) {
3947 if (! fitlimit
&& maxfit
>= 0)
3949 else if (! fitlimit
)
3966 * Output an encoded parameter string.
3970 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3971 size_t valueoff
, int index
)
3973 size_t outlen
= 0, n
;
3974 char *endptr
= output
+ len
, *p
;
3977 * First, output the marker for an encoded string.
3985 * If the index is 0, output the character set and language tag.
3986 * If theses were NULL, they should have already been filled in
3991 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3995 if (output
> endptr
) {
3996 advise(NULL
, "Internal error: parameter buffer overflow");
4002 * Copy over the value, encoding if necessary
4005 p
= pm
->pm_value
+ valueoff
;
4006 while (valuelen
-- > 0) {
4007 if (isparamencode(*p
)) {
4008 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4015 if (output
> endptr
) {
4016 advise(NULL
, "Internal error: parameter buffer overflow");
4027 * Output a "normal" parameter, without encoding. Be sure to escape
4028 * quotes and backslashes if necessary.
4032 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4036 char *endptr
= output
+ len
, *p
;
4042 p
= pm
->pm_value
+ valueoff
;
4044 while (valuelen
-- > 0) {
4055 if (output
> endptr
) {
4056 advise(NULL
, "Internal error: parameter buffer overflow");
4061 if (output
- 2 > endptr
) {
4062 advise(NULL
, "Internal error: parameter buffer overflow");
4073 * Add a parameter to the parameter linked list
4077 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4082 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4083 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4086 (*last
)->pm_next
= pm
;
4097 * Either replace a current parameter with a new value, or add the parameter
4098 * to the parameter linked list.
4102 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4106 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4107 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4109 * If nocopy is set, it's assumed that we own both name
4110 * and value. We don't need name, so we discard it now.
4115 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4120 return add_param(first
, last
, name
, value
, nocopy
);
4124 * Retrieve a parameter value from a parameter linked list. If the parameter
4125 * value needs converted to the local character set, do that now.
4129 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4131 while (first
!= NULL
) {
4132 if (strcasecmp(name
, first
->pm_name
) == 0) {
4134 return first
->pm_value
;
4135 return getcpy(get_param_value(first
, replace
));
4137 first
= first
->pm_next
;
4144 * Return a parameter value, converting to the local character set if
4148 char *get_param_value(PM pm
, char replace
)
4150 static char buffer
[4096]; /* I hope no parameters are larger */
4151 size_t bufsize
= sizeof(buffer
);
4156 ICONV_CONST
char *p
;
4157 #else /* HAVE_ICONV */
4159 #endif /* HAVE_ICONV */
4164 * If we don't have a character set indicated, it's assumed to be
4165 * US-ASCII. If it matches our character set, we don't need to convert
4169 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4170 strlen(pm
->pm_charset
))) {
4171 return pm
->pm_value
;
4175 * In this case, we need to convert. If we have iconv support, use
4176 * that. Otherwise, go through and simply replace every non-ASCII
4177 * character with the substitution character.
4182 bufsize
= sizeof(buffer
);
4183 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4185 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4186 if (cd
== (iconv_t
) -1) {
4190 inbytes
= strlen(pm
->pm_value
);
4194 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4195 if (errno
!= EILSEQ
) {
4200 * Reset shift state, substitute our character,
4201 * try to restart conversion.
4204 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4217 for (++p
, --inbytes
;
4218 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4237 #endif /* HAVE_ICONV */
4240 * Take everything non-ASCII and substitute the replacement character
4244 bufsize
= sizeof(buffer
);
4245 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4246 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))