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 (stat (file
, &statbuf
) == NOTOK
) {
249 advise (file
, "unable to stat");
251 } else if (S_ISDIR(statbuf
.st_mode
)) {
252 /* Don't try to parse a directory. */
253 inform("%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 inform("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 inform("message %s has unknown value for %s: field (%s), continuing...",
417 ct
->c_file
, VRSN_FIELD
, cp
);
422 if (! suppress_multiple_mime_version_warning
)
423 inform("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 inform("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 inform("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 inform("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 inform("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
) {
703 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
704 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
707 to_lower(ci
->ci_subtype
);
710 while (isspace ((unsigned char) *cp
))
713 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
714 &ci
->ci_comment
) == NOTOK
)
717 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
718 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
719 &ci
->ci_comment
)) != OK
) {
720 return status
== NOTOK
? NOTOK
: OK
;
724 * Get any <Content-Id> given in buffer
726 if (magic
&& *cp
== '<') {
729 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
730 inform("invalid ID in message %s", ct
->c_file
);
736 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
742 while (isspace ((unsigned char) *cp
))
747 * Get any [Content-Description] given in buffer.
749 if (magic
&& *cp
== '[') {
751 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
755 inform("invalid description in message %s", ct
->c_file
);
763 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
769 while (isspace ((unsigned char) *cp
))
774 * Get any {Content-Disposition} given in buffer.
776 if (magic
&& *cp
== '{') {
778 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
782 inform("invalid disposition in message %s", ct
->c_file
);
790 if (get_dispo(cp
, ct
, 1) != OK
)
796 while (isspace ((unsigned char) *cp
))
801 * Get any extension directives (right now just the content transfer
802 * encoding, but maybe others) that we care about.
805 if (magic
&& *cp
== '*') {
807 * See if it's a CTE we match on
812 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
816 inform("invalid null transfer encoding specification");
823 ct
->c_reqencoding
= CE_UNKNOWN
;
825 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
826 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
827 ct
->c_reqencoding
= kv
->kv_value
;
832 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
833 inform("invalid CTE specification: \"%s\"", dp
);
837 while (isspace ((unsigned char) *cp
))
842 * Check if anything is left over
846 ci
->ci_magic
= mh_xstrdup(cp
);
848 /* If there is a Content-Disposition header and it doesn't
849 have a *filename=, extract it from the magic contents.
850 The r1bindex call skips any leading directory
852 if (ct
->c_dispo_type
&&
853 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
854 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
855 r1bindex(ci
->ci_magic
, '/'), 0);
859 inform("extraneous information in message %s's %s: field\n%*s(%s)",
860 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
868 * Parse out a Content-Disposition header. A lot of this is cribbed from
872 get_dispo (char *cp
, CT ct
, int buildflag
)
874 char *dp
, *dispoheader
;
879 * Save the whole copy of the Content-Disposition header, unless we're
880 * processing a mhbuild directive. A NULL c_dispo will be a flag to
881 * mhbuild that the disposition header needs to be generated at that
885 dispoheader
= cp
= add(cp
, NULL
);
887 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
890 /* change newlines to spaces */
891 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
894 /* trim trailing spaces */
895 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
896 if (!isspace ((unsigned char) *dp
))
901 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
903 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
909 for (dp
= cp
; istoken (*dp
); dp
++)
912 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
915 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
918 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
919 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
921 if (status
== NOTOK
) {
926 inform("extraneous information in message %s's %s: field\n%*s(%s)",
927 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
933 ct
->c_dispo
= dispoheader
;
940 get_comment (const char *filename
, const char *fieldname
, char **ap
,
945 char c
, buffer
[BUFSIZ
], *dp
;
955 inform("invalid comment in message %s's %s: field",
956 filename
, fieldname
);
961 if ((c
= *cp
++) == '\0')
984 if ((dp
= *commentp
)) {
985 *commentp
= concat (dp
, " ", buffer
, NULL
);
988 *commentp
= mh_xstrdup(buffer
);
992 while (isspace ((unsigned char) *cp
))
1003 * Handles content types audio, image, and video.
1004 * There's not much to do right here.
1012 return OK
; /* not much to do here */
1023 char buffer
[BUFSIZ
];
1028 CI ci
= &ct
->c_ctinfo
;
1030 /* check for missing subtype */
1031 if (!*ci
->ci_subtype
)
1032 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1035 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1037 /* allocate text character set structure */
1039 ct
->c_ctparams
= (void *) t
;
1041 /* scan for charset parameter */
1042 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1043 if (!strcasecmp (pm
->pm_name
, "charset"))
1046 /* check if content specified a character set */
1048 chset
= pm
->pm_value
;
1049 t
->tx_charset
= CHARSET_SPECIFIED
;
1051 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1055 * If we can not handle character set natively,
1056 * then check profile for string to modify the
1057 * terminal or display method.
1059 * termproc is for mhshow, though mhlist -debug prints it, too.
1061 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1062 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1063 if ((cp
= context_find (buffer
)))
1064 ct
->c_termproc
= mh_xstrdup(cp
);
1076 InitMultiPart (CT ct
)
1086 struct multipart
*m
;
1087 struct part
*part
, **next
;
1088 CI ci
= &ct
->c_ctinfo
;
1093 * The encoding for multipart messages must be either
1094 * 7bit, 8bit, or binary (per RFC2045).
1096 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1097 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1098 /* Copy the Content-Transfer-Encoding header field body so we can
1099 remove any trailing whitespace and leading blanks from it. */
1100 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1102 bp
= cte
+ strlen (cte
) - 1;
1103 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1104 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1106 inform("\"%s/%s\" type in message %s must be encoded in\n"
1107 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1108 "mhfixmsg -fixcte can fix it, or\n"
1109 "manually edit the file and change the \"%s\"\n"
1110 "Content-Transfer-Encoding to one of those. For now, continuing...",
1111 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1118 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1121 * Check for "boundary" parameter, which is
1122 * required for multipart messages.
1125 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1126 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1132 /* complain if boundary parameter is missing */
1134 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1135 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1139 /* allocate primary structure for multipart info */
1141 ct
->c_ctparams
= (void *) m
;
1143 /* check if boundary parameter contains only whitespace characters */
1144 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1147 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1148 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1152 /* remove trailing whitespace from boundary parameter */
1153 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1154 if (!isspace ((unsigned char) *dp
))
1158 /* record boundary separators */
1159 m
->mp_start
= concat (bp
, "\n", NULL
);
1160 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1162 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1163 advise (ct
->c_file
, "unable to open for reading");
1167 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1169 next
= &m
->mp_parts
;
1173 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1178 if (bufp
[0] != '-' || bufp
[1] != '-')
1181 if (strcmp (bufp
+ 2, m
->mp_start
))
1186 next
= &part
->mp_next
;
1188 if (!(p
= get_content (fp
, ct
->c_file
,
1189 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1197 fseek (fp
, pos
, SEEK_SET
);
1200 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1204 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1205 if (p
->c_end
< p
->c_begin
)
1206 p
->c_begin
= p
->c_end
;
1211 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1216 if (! suppress_bogus_mp_content_warning
) {
1217 inform("bogus multipart content in message %s", ct
->c_file
);
1219 bogus_mp_content
= 1;
1221 if (!inout
&& part
) {
1223 p
->c_end
= ct
->c_end
;
1225 if (p
->c_begin
>= p
->c_end
) {
1226 for (next
= &m
->mp_parts
; *next
!= part
;
1227 next
= &((*next
)->mp_next
))
1236 /* reverse the order of the parts for multipart/alternative */
1237 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1243 * label all subparts with part number, and
1244 * then initialize the content of the subpart.
1249 char partnam
[BUFSIZ
];
1252 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1253 pp
= partnam
+ strlen (partnam
);
1258 for (part
= m
->mp_parts
, partnum
= 1; part
;
1259 part
= part
->mp_next
, partnum
++) {
1262 sprintf (pp
, "%d", partnum
);
1263 p
->c_partno
= mh_xstrdup(partnam
);
1265 /* initialize the content of the subparts */
1266 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1275 get_leftover_mp_content (ct
, 1);
1276 get_leftover_mp_content (ct
, 0);
1286 * reverse the order of the parts of a multipart/alternative,
1287 * presumably to put the "most favored" alternative first, for
1288 * ease of choosing/displaying it later on. from a mail message on
1289 * nmh-workers, from kenh:
1290 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1291 * see code in mhn that did the same thing... Acccording to the RCS
1292 * logs, that code was around from the initial checkin of mhn.c by
1293 * John Romine in 1992, which is as far back as we have."
1296 reverse_parts (CT ct
)
1298 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1302 /* Reverse the order of its parts by walking the mp_parts list
1303 and pushing each node to the front. */
1304 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1305 next
= part
->mp_next
;
1306 part
->mp_next
= m
->mp_parts
;
1312 move_preferred_part (CT ct
, char *type
, char *subtype
)
1314 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1315 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1319 /* move the matching part(s) to the head of the list: walk the
1320 * list of parts, move matching parts to a new list (maintaining
1321 * their order), and finally, concatenate the old list onto the
1328 head
->mp_next
= m
->mp_parts
;
1329 nhead
->mp_next
= NULL
;
1333 part
= head
->mp_next
;
1334 while (part
!= NULL
) {
1335 ci
= &part
->mp_part
->c_ctinfo
;
1336 if (!strcasecmp(ci
->ci_type
, type
) &&
1337 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1338 prev
->mp_next
= part
->mp_next
;
1339 part
->mp_next
= NULL
;
1340 ntail
->mp_next
= part
;
1342 part
= prev
->mp_next
;
1345 part
= prev
->mp_next
;
1348 ntail
->mp_next
= head
->mp_next
;
1349 m
->mp_parts
= nhead
->mp_next
;
1354 * move parts that match the user's preferences (-prefer) to the head
1355 * of the line. process preferences in reverse so first one given
1356 * ends up first in line
1362 for (i
= npreferred
-1; i
>= 0; i
--)
1363 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1368 /* parse_mime() arranges alternates in reverse (priority) order. This
1369 function can be used to reverse them back. This will put, for
1370 example, a text/plain part before a text/html part in a
1371 multipart/alternative part, for example, where it belongs. */
1373 reverse_alternative_parts (CT ct
) {
1374 if (ct
->c_type
== CT_MULTIPART
) {
1375 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1378 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1382 /* And call recursively on each part of a multipart. */
1383 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1384 reverse_alternative_parts (part
->mp_part
);
1397 CI ci
= &ct
->c_ctinfo
;
1399 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1400 inform("\"%s/%s\" type in message %s should be encoded in "
1401 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1406 /* check for missing subtype */
1407 if (!*ci
->ci_subtype
)
1408 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1411 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1413 switch (ct
->c_subtype
) {
1414 case MESSAGE_RFC822
:
1417 case MESSAGE_PARTIAL
:
1423 ct
->c_ctparams
= (void *) p
;
1425 /* scan for parameters "id", "number", and "total" */
1426 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1427 if (!strcasecmp (pm
->pm_name
, "id")) {
1428 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1431 if (!strcasecmp (pm
->pm_name
, "number")) {
1432 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1433 || p
->pm_partno
< 1) {
1435 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1436 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1437 ct
->c_file
, TYPE_FIELD
);
1442 if (!strcasecmp (pm
->pm_name
, "total")) {
1443 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1452 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1453 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1454 ci
->ci_type
, ci
->ci_subtype
, 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
) {
1639 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1640 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1653 InitApplication (CT ct
)
1655 CI ci
= &ct
->c_ctinfo
;
1658 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1665 * TRANSFER ENCODINGS
1669 init_encoding (CT ct
, OpenCEFunc openfnx
)
1671 ct
->c_ceopenfnx
= openfnx
;
1672 ct
->c_ceclosefnx
= close_encoding
;
1673 ct
->c_cesizefnx
= size_encoding
;
1680 close_encoding (CT ct
)
1682 CE ce
= &ct
->c_cefile
;
1691 static unsigned long
1692 size_encoding (CT ct
)
1697 CE ce
= &ct
->c_cefile
;
1700 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1701 return (long) st
.st_size
;
1704 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1705 return (long) st
.st_size
;
1709 if (ct
->c_encoding
== CE_EXTERNAL
)
1710 return (ct
->c_end
- ct
->c_begin
);
1713 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1714 return (ct
->c_end
- ct
->c_begin
);
1716 if (fstat (fd
, &st
) != NOTOK
)
1717 size
= (long) st
.st_size
;
1721 (*ct
->c_ceclosefnx
) (ct
);
1733 return init_encoding (ct
, openBase64
);
1738 openBase64 (CT ct
, char **file
)
1741 int fd
, own_ct_fp
= 0;
1742 char *cp
, *buffer
= NULL
;
1743 /* sbeck -- handle suffixes */
1745 CE ce
= &ct
->c_cefile
;
1746 unsigned char *decoded
;
1748 unsigned char digest
[16];
1751 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1756 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1757 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1763 if (*file
== NULL
) {
1766 ce
->ce_file
= mh_xstrdup(*file
);
1770 /* sbeck@cise.ufl.edu -- handle suffixes */
1772 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1773 if (ce
->ce_unlink
) {
1774 /* Create temporary file with filename extension. */
1775 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1776 adios(NULL
, "unable to create temporary file in %s",
1780 ce
->ce_file
= add (cp
, ce
->ce_file
);
1782 } else if (*file
== NULL
) {
1784 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1785 adios(NULL
, "unable to create temporary file in %s",
1788 ce
->ce_file
= mh_xstrdup(tempfile
);
1791 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1792 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1796 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1797 adios (NULL
, "internal error(1)");
1799 buffer
= mh_xmalloc (len
+ 1);
1802 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1803 content_error (ct
->c_file
, ct
, "unable to open for reading");
1809 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1812 switch (cc
= read (fd
, cp
, len
)) {
1814 content_error (ct
->c_file
, ct
, "error reading from");
1818 content_error (NULL
, ct
, "premature eof");
1829 /* decodeBase64() requires null-terminated input. */
1832 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1833 ct
->c_digested
? digest
: NULL
) == OK
) {
1835 unsigned char *decoded_p
= decoded
;
1836 for (i
= 0; i
< decoded_len
; ++i
) {
1837 putc (*decoded_p
++, ce
->ce_fp
);
1840 if (ferror (ce
->ce_fp
)) {
1841 content_error (ce
->ce_file
, ct
, "error writing to");
1845 if (ct
->c_digested
) {
1846 if (memcmp(digest
, ct
->c_digest
,
1847 sizeof(digest
) / sizeof(digest
[0]))) {
1848 content_error (NULL
, ct
,
1849 "content integrity suspect (digest mismatch) -- continuing");
1852 fprintf (stderr
, "content integrity confirmed\n");
1860 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1862 if (fflush (ce
->ce_fp
)) {
1863 content_error (ce
->ce_file
, ct
, "error writing to");
1867 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1870 *file
= ce
->ce_file
;
1876 return fileno (ce
->ce_fp
);
1883 free_encoding (ct
, 0);
1893 static char hex2nib
[0x80] = {
1894 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
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, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1901 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1902 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1909 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1916 return init_encoding (ct
, openQuoted
);
1921 openQuoted (CT ct
, char **file
)
1923 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1929 CE ce
= &ct
->c_cefile
;
1930 /* sbeck -- handle suffixes */
1935 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1940 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1941 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1947 if (*file
== NULL
) {
1950 ce
->ce_file
= mh_xstrdup(*file
);
1954 /* sbeck@cise.ufl.edu -- handle suffixes */
1956 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1957 if (ce
->ce_unlink
) {
1958 /* Create temporary file with filename extension. */
1959 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1960 adios(NULL
, "unable to create temporary file in %s",
1964 ce
->ce_file
= add (cp
, ce
->ce_file
);
1966 } else if (*file
== NULL
) {
1968 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1969 adios(NULL
, "unable to create temporary file in %s",
1972 ce
->ce_file
= mh_xstrdup(tempfile
);
1975 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1976 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1980 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1981 adios (NULL
, "internal error(2)");
1984 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1985 content_error (ct
->c_file
, ct
, "unable to open for reading");
1991 if ((digested
= ct
->c_digested
))
1992 MD5Init (&mdContext
);
1999 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2001 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2002 content_error (NULL
, ct
, "premature eof");
2006 if ((cc
= gotlen
) > len
)
2010 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2011 if (!isspace ((unsigned char) *ep
))
2015 for (; cp
< ep
; cp
++) {
2017 /* in an escape sequence */
2019 /* at byte 1 of an escape sequence */
2020 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2021 /* next is byte 2 */
2024 /* at byte 2 of an escape sequence */
2026 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2027 putc (mask
, ce
->ce_fp
);
2029 MD5Update (&mdContext
, &mask
, 1);
2030 if (ferror (ce
->ce_fp
)) {
2031 content_error (ce
->ce_file
, ct
, "error writing to");
2034 /* finished escape sequence; next may be literal or a new
2035 * escape sequence */
2038 /* on to next byte */
2042 /* not in an escape sequence */
2044 /* starting an escape sequence, or invalid '='? */
2045 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2046 /* "=\n" soft line break, eat the \n */
2050 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2051 /* We don't have 2 bytes left, so this is an invalid
2052 * escape sequence; just show the raw bytes (below). */
2053 } else if (isxdigit ((unsigned char) cp
[1]) &&
2054 isxdigit ((unsigned char) cp
[2])) {
2055 /* Next 2 bytes are hex digits, making this a valid escape
2056 * sequence; let's decode it (above). */
2060 /* One or both of the next 2 is out of range, making this
2061 * an invalid escape sequence; just show the raw bytes
2066 /* Just show the raw byte. */
2067 putc (*cp
, ce
->ce_fp
);
2070 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2072 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2075 if (ferror (ce
->ce_fp
)) {
2076 content_error (ce
->ce_file
, ct
, "error writing to");
2082 content_error (NULL
, ct
,
2083 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2087 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2089 if (fflush (ce
->ce_fp
)) {
2090 content_error (ce
->ce_file
, ct
, "error writing to");
2095 unsigned char digest
[16];
2097 MD5Final (digest
, &mdContext
);
2098 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2099 sizeof(digest
) / sizeof(digest
[0])))
2100 content_error (NULL
, ct
,
2101 "content integrity suspect (digest mismatch) -- continuing");
2104 fprintf (stderr
, "content integrity confirmed\n");
2107 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2110 *file
= ce
->ce_file
;
2116 return fileno (ce
->ce_fp
);
2119 free_encoding (ct
, 0);
2136 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2139 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2145 open7Bit (CT ct
, char **file
)
2147 int cc
, fd
, len
, own_ct_fp
= 0;
2148 char buffer
[BUFSIZ
];
2149 /* sbeck -- handle suffixes */
2152 CE ce
= &ct
->c_cefile
;
2155 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2160 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2161 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2167 if (*file
== NULL
) {
2170 ce
->ce_file
= mh_xstrdup(*file
);
2174 /* sbeck@cise.ufl.edu -- handle suffixes */
2176 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2177 if (ce
->ce_unlink
) {
2178 /* Create temporary file with filename extension. */
2179 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2180 adios(NULL
, "unable to create temporary file in %s",
2184 ce
->ce_file
= add (cp
, ce
->ce_file
);
2186 } else if (*file
== NULL
) {
2188 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2189 adios(NULL
, "unable to create temporary file in %s",
2192 ce
->ce_file
= mh_xstrdup(tempfile
);
2195 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2196 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2200 if (ct
->c_type
== CT_MULTIPART
) {
2201 CI ci
= &ct
->c_ctinfo
;
2205 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2206 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2207 + 1 + strlen (ci
->ci_subtype
);
2208 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2211 fputs (buffer
, ce
->ce_fp
);
2215 if (ci
->ci_comment
) {
2216 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2217 fputs ("\n\t", ce
->ce_fp
);
2221 putc (' ', ce
->ce_fp
);
2224 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2227 fprintf (ce
->ce_fp
, "\n");
2229 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2231 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2233 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2234 fprintf (ce
->ce_fp
, "\n");
2237 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2238 adios (NULL
, "internal error(3)");
2241 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2242 content_error (ct
->c_file
, ct
, "unable to open for reading");
2248 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2250 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2252 content_error (ct
->c_file
, ct
, "error reading from");
2256 content_error (NULL
, ct
, "premature eof");
2264 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2265 advise ("open7Bit", "fwrite");
2267 if (ferror (ce
->ce_fp
)) {
2268 content_error (ce
->ce_file
, ct
, "error writing to");
2273 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2275 if (fflush (ce
->ce_fp
)) {
2276 content_error (ce
->ce_file
, ct
, "error writing to");
2280 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2283 *file
= ce
->ce_file
;
2288 return fileno (ce
->ce_fp
);
2291 free_encoding (ct
, 0);
2305 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2307 char cachefile
[BUFSIZ
];
2310 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2315 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2316 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2322 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2323 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2324 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2325 ce
->ce_file
= mh_xstrdup(cachefile
);
2329 admonish (cachefile
, "unable to fopen for reading");
2332 *fd
= fileno (ce
->ce_fp
);
2336 *file
= ce
->ce_file
;
2337 *fd
= fileno (ce
->ce_fp
);
2348 return init_encoding (ct
, openFile
);
2353 openFile (CT ct
, char **file
)
2356 char cachefile
[BUFSIZ
];
2357 struct exbody
*e
= ct
->c_ctexbody
;
2358 CE ce
= &ct
->c_cefile
;
2360 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2372 content_error (NULL
, ct
, "missing name parameter");
2376 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2379 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2380 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2384 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2385 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2386 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2390 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2391 if ((fp
= fopen (cachefile
, "w"))) {
2393 char buffer
[BUFSIZ
];
2394 FILE *gp
= ce
->ce_fp
;
2396 fseek (gp
, 0L, SEEK_SET
);
2398 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2400 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2401 advise ("openFile", "fwrite");
2406 admonish (ce
->ce_file
, "error reading");
2407 (void) m_unlink (cachefile
);
2411 admonish (cachefile
, "error writing");
2412 (void) m_unlink (cachefile
);
2419 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2420 *file
= ce
->ce_file
;
2421 return fileno (ce
->ce_fp
);
2431 return init_encoding (ct
, openFTP
);
2436 openFTP (CT ct
, char **file
)
2438 int cachetype
, caching
, fd
;
2440 char *bp
, *ftp
, *user
, *pass
;
2441 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2443 CE ce
= &ct
->c_cefile
;
2444 static char *username
= NULL
;
2445 static char *password
= NULL
;
2449 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2455 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2466 if (!e
->eb_name
|| !e
->eb_site
) {
2467 content_error (NULL
, ct
, "missing %s parameter",
2468 e
->eb_name
? "site": "name");
2472 /* Get the buffer ready to go */
2474 buflen
= sizeof(buffer
);
2477 * Construct the query message for user
2479 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2485 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2491 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2492 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2497 if (e
->eb_size
> 0) {
2498 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2503 snprintf (bp
, buflen
, "? ");
2506 * Now, check the answer
2508 if (!read_yes_or_no_if_tty (buffer
))
2513 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2517 ruserpass (e
->eb_site
, &username
, &password
, 0);
2522 ce
->ce_unlink
= (*file
== NULL
);
2524 cachefile
[0] = '\0';
2525 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2526 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2527 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2528 if (*file
== NULL
) {
2535 ce
->ce_file
= mh_xstrdup(*file
);
2537 ce
->ce_file
= mh_xstrdup(cachefile
);
2540 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2541 adios(NULL
, "unable to create temporary file in %s",
2544 ce
->ce_file
= mh_xstrdup(tempfile
);
2547 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2548 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2553 int child_id
, i
, vecp
;
2557 vec
[vecp
++] = r1bindex (ftp
, '/');
2558 vec
[vecp
++] = e
->eb_site
;
2561 vec
[vecp
++] = e
->eb_dir
;
2562 vec
[vecp
++] = e
->eb_name
;
2563 vec
[vecp
++] = ce
->ce_file
,
2564 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2565 ? "ascii" : "binary";
2570 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2574 adios ("fork", "unable to");
2578 close (fileno (ce
->ce_fp
));
2580 fprintf (stderr
, "unable to exec ");
2586 if (pidXwait (child_id
, NULL
)) {
2587 username
= password
= NULL
;
2597 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2602 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2603 if ((fp
= fopen (cachefile
, "w"))) {
2605 FILE *gp
= ce
->ce_fp
;
2607 fseek (gp
, 0L, SEEK_SET
);
2609 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2611 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2612 advise ("openFTP", "fwrite");
2617 admonish (ce
->ce_file
, "error reading");
2618 (void) m_unlink (cachefile
);
2622 admonish (cachefile
, "error writing");
2623 (void) m_unlink (cachefile
);
2631 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2632 *file
= ce
->ce_file
;
2633 return fileno (ce
->ce_fp
);
2644 return init_encoding (ct
, openMail
);
2649 openMail (CT ct
, char **file
)
2651 int child_id
, fd
, i
, vecp
;
2653 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2654 struct exbody
*e
= ct
->c_ctexbody
;
2655 CE ce
= &ct
->c_cefile
;
2657 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2668 if (!e
->eb_server
) {
2669 content_error (NULL
, ct
, "missing server parameter");
2673 /* Get buffer ready to go */
2675 buflen
= sizeof(buffer
);
2677 /* Now, construct query message */
2678 snprintf (bp
, buflen
, "Retrieve content");
2684 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2690 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2692 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2694 /* Now, check answer */
2695 if (!read_yes_or_no_if_tty (buffer
))
2699 vec
[vecp
++] = r1bindex (mailproc
, '/');
2700 vec
[vecp
++] = e
->eb_server
;
2701 vec
[vecp
++] = "-subject";
2702 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2703 vec
[vecp
++] = "-body";
2704 vec
[vecp
++] = e
->eb_body
;
2707 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2711 advise ("fork", "unable to");
2715 execvp (mailproc
, vec
);
2716 fprintf (stderr
, "unable to exec ");
2722 if (pidXwait (child_id
, NULL
) == OK
)
2723 inform("request sent");
2727 if (*file
== NULL
) {
2729 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2730 adios(NULL
, "unable to create temporary file in %s",
2733 ce
->ce_file
= mh_xstrdup(tempfile
);
2736 ce
->ce_file
= mh_xstrdup(*file
);
2740 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2741 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2745 /* showproc is for mhshow and mhstore, though mhlist -debug
2746 * prints it, too. */
2747 mh_xfree(ct
->c_showproc
);
2748 ct
->c_showproc
= mh_xstrdup("true");
2750 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2751 *file
= ce
->ce_file
;
2752 return fileno (ce
->ce_fp
);
2763 return init_encoding (ct
, openURL
);
2768 openURL (CT ct
, char **file
)
2770 struct exbody
*e
= ct
->c_ctexbody
;
2771 CE ce
= &ct
->c_cefile
;
2772 char *urlprog
, *program
;
2773 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2774 int fd
, caching
, cachetype
;
2775 struct msgs_array args
= { 0, 0, NULL
};
2778 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2782 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2786 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2798 content_error(NULL
, ct
, "missing url parameter");
2802 ce
->ce_unlink
= (*file
== NULL
);
2804 cachefile
[0] = '\0';
2806 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2807 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2808 if (*file
== NULL
) {
2815 ce
->ce_file
= mh_xstrdup(*file
);
2817 ce
->ce_file
= mh_xstrdup(cachefile
);
2820 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2821 adios(NULL
, "unable to create temporary file in %s",
2824 ce
->ce_file
= mh_xstrdup(tempfile
);
2827 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2828 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2832 switch (child_id
= fork()) {
2834 adios ("fork", "unable to");
2838 argsplit_msgarg(&args
, urlprog
, &program
);
2839 app_msgarg(&args
, e
->eb_url
);
2840 app_msgarg(&args
, NULL
);
2841 dup2(fileno(ce
->ce_fp
), 1);
2842 close(fileno(ce
->ce_fp
));
2843 execvp(program
, args
.msgs
);
2844 fprintf(stderr
, "Unable to exec ");
2850 if (pidXwait(child_id
, NULL
)) {
2858 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2863 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2864 if ((fp
= fopen(cachefile
, "w"))) {
2866 FILE *gp
= ce
->ce_fp
;
2868 fseeko(gp
, 0, SEEK_SET
);
2870 while ((cc
= fread(buffer
, sizeof(*buffer
),
2871 sizeof(buffer
), gp
)) > 0)
2872 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2873 advise ("openURL", "fwrite");
2879 admonish(ce
->ce_file
, "error reading");
2880 (void) m_unlink (cachefile
);
2887 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2888 *file
= ce
->ce_file
;
2894 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2895 * has to be base64 decoded.
2898 readDigest (CT ct
, char *cp
)
2900 unsigned char *digest
;
2903 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2904 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2906 if (strlen ((char *) digest
) <= maxlen
) {
2907 memcpy (ct
->c_digest
, digest
, maxlen
);
2912 fprintf (stderr
, "MD5 digest=");
2913 for (i
= 0; i
< maxlen
; ++i
) {
2914 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2916 fprintf (stderr
, "\n");
2922 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2923 (int) strlen ((char *) digest
));
2933 /* Multipart parts might have content before the first subpart and/or
2934 after the last subpart that hasn't been stored anywhere else, so do
2937 get_leftover_mp_content (CT ct
, int before
/* or after */)
2939 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2941 int found_boundary
= 0;
2947 char *content
= NULL
;
2949 if (! m
) return NOTOK
;
2952 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2954 /* Isolate the beginning of this part to the beginning of the
2955 first subpart and save any content between them. */
2956 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2957 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2958 boundary
= concat ("--", m
->mp_start
, NULL
);
2960 struct part
*last_subpart
= NULL
;
2961 struct part
*subpart
;
2963 /* Go to the last subpart to get its end position. */
2964 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2965 last_subpart
= subpart
;
2968 if (last_subpart
== NULL
) return NOTOK
;
2970 /* Isolate the end of the last subpart to the end of this part
2971 and save any content between them. */
2972 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2973 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2974 boundary
= concat ("--", m
->mp_stop
, NULL
);
2977 /* Back up by 1 to pick up the newline. */
2978 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2980 /* Don't look beyond beginning of first subpart (before) or
2981 next part (after). */
2982 if (read
> max
) bufp
[read
-max
] = '\0';
2985 if (! strcmp (bufp
, boundary
)) {
2989 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2995 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2997 char *old_content
= content
;
2998 content
= concat (content
, bufp
, NULL
);
3002 ? concat ("\n", bufp
, NULL
)
3003 : concat (bufp
, NULL
);
3008 if (found_boundary
|| read
> max
) break;
3010 if (read
> max
) break;
3014 /* Skip the newline if that's all there is. */
3018 /* Remove trailing newline, except at EOF. */
3019 if ((before
|| ! feof (ct
->c_fp
)) &&
3020 (cp
= content
+ strlen (content
)) > content
&&
3025 if (strlen (content
) > 1) {
3027 m
->mp_content_before
= content
;
3029 m
->mp_content_after
= content
;
3044 ct_type_str (int type
) {
3046 case CT_APPLICATION
:
3047 return "application";
3063 return "unknown_type";
3069 ct_subtype_str (int type
, int subtype
) {
3071 case CT_APPLICATION
:
3073 case APPLICATION_OCTETS
:
3075 case APPLICATION_POSTSCRIPT
:
3076 return "postscript";
3078 return "unknown_app_subtype";
3082 case MESSAGE_RFC822
:
3084 case MESSAGE_PARTIAL
:
3086 case MESSAGE_EXTERNAL
:
3089 return "unknown_msg_subtype";
3095 case MULTI_ALTERNATE
:
3096 return "alternative";
3099 case MULTI_PARALLEL
:
3104 return "unknown_multipart_subtype";
3115 return "unknown_text_subtype";
3118 return "unknown_type";
3124 ct_str_type (const char *type
) {
3125 struct str2init
*s2i
;
3127 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3128 if (! strcasecmp (type
, s2i
->si_key
)) {
3132 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3141 ct_str_subtype (int type
, const char *subtype
) {
3145 case CT_APPLICATION
:
3146 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3147 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3151 return kv
->kv_value
;
3153 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3154 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3158 return kv
->kv_value
;
3160 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3161 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3165 return kv
->kv_value
;
3167 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3168 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3172 return kv
->kv_value
;
3179 /* Find the content type and InitFunc for the CT. */
3180 const struct str2init
*
3181 get_ct_init (int type
) {
3182 const struct str2init
*sp
;
3184 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3185 if (type
== sp
->si_val
) {
3194 ce_str (int encoding
) {
3199 return "quoted-printable";
3215 /* Find the content type and InitFunc for the content encoding method. */
3216 const struct str2init
*
3217 get_ce_method (const char *method
) {
3218 struct str2init
*sp
;
3220 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3221 if (! strcasecmp (method
, sp
->si_key
)) {
3230 * Parse a series of MIME attributes (or parameters) given a header as
3233 * Arguments include:
3235 * filename - Name of input file (for error messages)
3236 * fieldname - Name of field being processed
3237 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3238 * Updated to point to end of attributes when finished.
3239 * param_head - Pointer to head of parameter list
3240 * param_tail - Pointer to tail of parameter list
3241 * commentp - Pointer to header comment pointer (may be NULL)
3243 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3244 * DONE to indicate a benign error (minor parsing error, but the program
3249 parse_header_attrs (const char *filename
, const char *fieldname
,
3250 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3253 char *cp
= *header_attrp
;
3259 struct sectlist
*next
;
3265 struct sectlist
*sechead
;
3266 struct parmlist
*next
;
3267 } *pp
, *pp2
, *phead
= NULL
;
3269 while (*cp
== ';') {
3270 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3271 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3274 while (isspace ((unsigned char) *cp
))
3278 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3283 if (! suppress_extraneous_trailing_semicolon_warning
) {
3284 inform("extraneous trailing ';' in message %s's %s: "
3285 "parameter list", filename
, fieldname
);
3290 /* down case the attribute name */
3291 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3292 *dp
= tolower ((unsigned char) *dp
);
3294 for (up
= dp
; isspace ((unsigned char) *dp
);)
3296 if (dp
== cp
|| *dp
!= '=') {
3297 inform("invalid parameter in message %s's %s: "
3298 "field\n%*sparameter %s (error detected at offset %d)",
3299 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3304 * To handle RFC 2231, we have to deal with the following extensions:
3306 * name*=encoded-value
3307 * name*<N>=part-N-of-a-parameter-value
3308 * name*<N>*=encoded-part-N-of-a-parameter-value
3311 * If there's a * right before the equal sign, it's encoded.
3312 * If there's a * and one or more digits, then it's section N.
3314 * Remember we can have one or the other, or both. cp points to
3315 * beginning of name, up points past the last character in the
3319 for (vp
= cp
; vp
< up
; vp
++) {
3320 if (*vp
== '*' && vp
< up
- 1) {
3323 } else if (*vp
== '*' && vp
== up
- 1) {
3325 } else if (partial
) {
3326 if (isdigit((unsigned char) *vp
))
3327 index
= *vp
- '0' + index
* 10;
3329 inform("invalid parameter index in message %s's "
3330 "%s: field\n%*s(parameter %s)", filename
,
3331 fieldname
, strlen(invo_name
) + 2, "", cp
);
3340 * Break out the parameter name and value sections and allocate
3344 nameptr
= mh_xmalloc(len
+ 1);
3345 strncpy(nameptr
, cp
, len
);
3346 nameptr
[len
] = '\0';
3348 for (dp
++; isspace ((unsigned char) *dp
);)
3353 * Single quotes delimit the character set and language tag.
3354 * They are required on the first section (or a complete
3359 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3365 charset
= mh_xmalloc(len
+ 1);
3366 strncpy(charset
, dp
, len
);
3367 charset
[len
] = '\0';
3373 inform("missing charset in message %s's %s: "
3374 "field\n%*s(parameter %s)", filename
, fieldname
,
3375 strlen(invo_name
) + 2, "", nameptr
);
3381 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3388 lang
= mh_xmalloc(len
+ 1);
3389 strncpy(lang
, dp
, len
);
3396 inform("missing language tag in message %s's %s: "
3397 "field\n%*s(parameter %s)", filename
, fieldname
,
3398 strlen(invo_name
) + 2, "", nameptr
);
3408 * At this point vp should be pointing at the beginning
3409 * of the encoded value/section. Continue until we reach
3410 * the end or get whitespace. But first, calculate the
3411 * length so we can allocate the correct buffer size.
3414 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3416 if (*(vp
+ 1) == '\0' ||
3417 !isxdigit((unsigned char) *(vp
+ 1)) ||
3418 *(vp
+ 2) == '\0' ||
3419 !isxdigit((unsigned char) *(vp
+ 2))) {
3420 inform("invalid encoded sequence in message "
3421 "%s's %s: field\n%*s(parameter %s)",
3422 filename
, fieldname
, strlen(invo_name
) + 2,
3434 up
= valptr
= mh_xmalloc(len
+ 1);
3436 for (vp
= dp
; istoken(*vp
); vp
++) {
3438 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3449 * A "normal" string. If it's got a leading quote, then we
3450 * strip the quotes out. Otherwise go until we reach the end
3451 * or get whitespace. Note we scan it twice; once to get the
3452 * length, then the second time copies it into the destination
3459 for (cp
= dp
+ 1;;) {
3463 inform("invalid quoted-string in message %s's %s: "
3464 "field\n%*s(parameter %s)", filename
,
3465 fieldname
, strlen(invo_name
) + 2, "", nameptr
);
3485 for (cp
= dp
; istoken (*cp
); cp
++) {
3490 valptr
= mh_xmalloc(len
+ 1);
3494 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3502 strncpy(valptr
, cp
= dp
, len
);
3510 * If 'partial' is set, we don't allocate a parameter now. We
3511 * put it on the parameter linked list to be reassembled later.
3513 * "phead" points to a list of all parameters we need to reassemble.
3514 * Each parameter has a list of sections. We insert the sections in
3519 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3520 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3535 * Insert this into the section linked list
3543 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3544 sp
->next
= pp
->sechead
;
3547 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3548 if (sp2
->index
== sp
->index
) {
3549 inform("duplicate index (%d) in message "
3550 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3551 filename
, fieldname
, strlen(invo_name
) + 2, "",
3555 if (sp2
->index
< sp
->index
&&
3556 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3557 sp
->next
= sp2
->next
;
3564 inform("Internal error: cannot insert partial "
3565 "param in message %s's %s: field\n%*s(parameter %s)",
3566 filename
, fieldname
, strlen(invo_name
) + 2, "",
3573 * Save our charset and lang tags.
3576 if (index
== 0 && encoded
) {
3577 mh_xfree(pp
->charset
);
3578 pp
->charset
= charset
;
3583 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3584 pm
->pm_charset
= charset
;
3588 while (isspace ((unsigned char) *cp
))
3592 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3598 * Now that we're done, reassemble all of the partial parameters.
3601 for (pp
= phead
; pp
!= NULL
; ) {
3605 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3606 if (sp
->index
!= pindex
++) {
3607 inform("missing section %d for parameter in "
3608 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3609 filename
, fieldname
, strlen(invo_name
) + 2, "",
3616 p
= q
= mh_xmalloc(tlen
+ 1);
3617 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3618 memcpy(q
, sp
->value
, sp
->len
);
3628 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3629 pm
->pm_charset
= pp
->charset
;
3630 pm
->pm_lang
= pp
->lang
;
3641 * Return the charset for a particular content type.
3645 content_charset (CT ct
) {
3646 char *ret_charset
= NULL
;
3648 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3650 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3655 * Create a string based on a list of output parameters. Assume that this
3656 * parameter string will be appended to an existing header, so start out
3657 * with the separator (;). Perform RFC 2231 encoding when necessary.
3661 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3663 char *paramout
= NULL
;
3664 char line
[CPERLIN
* 2], *q
;
3665 int curlen
, index
, cont
, encode
, i
;
3666 size_t valoff
, numchars
;
3668 while (params
!= NULL
) {
3674 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3677 if (strlen(params
->pm_name
) > CPERLIN
) {
3678 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3683 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3686 * Loop until we get a parameter that fits within a line. We
3687 * assume new lines start with a tab, so check our overflow based
3697 * At this point we're definitely continuing the line, so
3698 * be sure to include the parameter name and section index.
3701 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3702 params
->pm_name
, index
);
3705 * Both of these functions do a NUL termination
3709 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3710 numchars
, valoff
, index
);
3712 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3722 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3727 * "line" starts with a ;\n\t, so that doesn't count against
3728 * the length. But add 8 since it starts with a tab; that's
3729 * how we end up with 5.
3732 initialwidth
= strlen(line
) + 5;
3735 * At this point the line should be built, so add it to our
3736 * current output buffer.
3739 paramout
= add(line
, paramout
);
3743 * If this won't fit on the line, start a new one. Save room in
3744 * case we need a semicolon on the end
3747 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3759 * At this point, we're either finishing a contined parameter, or
3760 * we're working on a new one.
3764 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3765 params
->pm_name
, index
);
3767 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3772 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3773 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3775 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3776 strlen(params
->pm_value
+ valoff
), valoff
);
3783 paramout
= add(line
, paramout
);
3784 initialwidth
+= strlen(line
);
3786 params
= params
->pm_next
;
3790 *offsetout
= initialwidth
;
3796 * Calculate the size of a parameter.
3800 * pm - The parameter being output
3801 * index - If continuing the parameter, the index of the section
3803 * valueoff - The current offset into the parameter value that we're
3804 * working on (previous sections have consumed valueoff bytes).
3805 * encode - Set if we should perform encoding on this parameter section
3806 * (given that we're consuming bytesfit bytes).
3807 * cont - Set if the remaining data in value will not fit on a single
3808 * line and will need to be continued.
3809 * bytesfit - The number of bytes that we can consume from the parameter
3810 * value and still fit on a completely new line. The
3811 * calculation assumes the new line starts with a tab,
3812 * includes the parameter name and any encoding, and fits
3813 * within CPERLIN bytes. Will always be at least 1.
3817 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3820 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3821 size_t len
= 0, fit
= 0;
3822 int fitlimit
= 0, eightbit
, maxfit
;
3827 * Add up the length. First, start with the parameter name.
3830 len
= strlen(pm
->pm_name
);
3833 * Scan the parameter value and see if we need to do encoding for this
3837 eightbit
= contains8bit(start
, NULL
);
3840 * Determine if we need to encode this section. Encoding is necessary if:
3842 * - There are any 8-bit characters at all and we're on the first
3844 * - There are 8-bit characters within N bytes of our section start.
3845 * N is calculated based on the number of bytes it would take to
3846 * reach CPERLIN. Specifically:
3847 * 8 (starting tab) +
3848 * strlen(param name) +
3849 * 4 ('* for section marker, '=', opening/closing '"')
3851 * is the number of bytes used by everything that isn't part of the
3852 * value. So that gets subtracted from CPERLIN.
3855 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3856 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3857 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3861 len
++; /* Add in equal sign */
3865 * We're using maxfit as a marker for how many characters we can
3866 * fit into the line. Bump it by two because we're not using quotes
3873 * If we don't have a charset or language tag in this parameter,
3877 if (! pm
->pm_charset
) {
3878 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3879 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3880 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3881 "local character set is US-ASCII", pm
->pm_name
);
3884 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3886 len
++; /* For the encoding marker */
3889 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3894 * We know we definitely need to include an index. maxfit already
3895 * includes the section marker.
3897 len
+= strlen(indexchar
);
3899 for (p
= start
; *p
!= '\0'; p
++) {
3900 if (isparamencode(*p
)) {
3908 * Just so there's no confusion: maxfit is counting OUTPUT
3909 * characters (post-encoding). fit is counting INPUT characters.
3911 if (! fitlimit
&& maxfit
>= 0)
3913 else if (! fitlimit
)
3918 * Calculate the string length, but add room for quoting \
3919 * and " if necessary. Also account for quotes at beginning
3922 for (p
= start
; *p
!= '\0'; p
++) {
3933 if (! fitlimit
&& maxfit
>= 0)
3935 else if (! fitlimit
)
3952 * Output an encoded parameter string.
3956 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3957 size_t valueoff
, int index
)
3959 size_t outlen
= 0, n
;
3960 char *endptr
= output
+ len
, *p
;
3963 * First, output the marker for an encoded string.
3971 * If the index is 0, output the character set and language tag.
3972 * If theses were NULL, they should have already been filled in
3977 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3981 if (output
> endptr
) {
3982 inform("Internal error: parameter buffer overflow");
3988 * Copy over the value, encoding if necessary
3991 p
= pm
->pm_value
+ valueoff
;
3992 while (valuelen
-- > 0) {
3993 if (isparamencode(*p
)) {
3994 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4001 if (output
> endptr
) {
4002 inform("Internal error: parameter buffer overflow");
4013 * Output a "normal" parameter, without encoding. Be sure to escape
4014 * quotes and backslashes if necessary.
4018 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4022 char *endptr
= output
+ len
, *p
;
4028 p
= pm
->pm_value
+ valueoff
;
4030 while (valuelen
-- > 0) {
4041 if (output
> endptr
) {
4042 inform("Internal error: parameter buffer overflow");
4047 if (output
- 2 > endptr
) {
4048 inform("Internal error: parameter buffer overflow");
4059 * Add a parameter to the parameter linked list
4063 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4068 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4069 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4072 (*last
)->pm_next
= pm
;
4083 * Either replace a current parameter with a new value, or add the parameter
4084 * to the parameter linked list.
4088 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4092 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4093 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4095 * If nocopy is set, it's assumed that we own both name
4096 * and value. We don't need name, so we discard it now.
4101 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4106 return add_param(first
, last
, name
, value
, nocopy
);
4110 * Retrieve a parameter value from a parameter linked list. If the parameter
4111 * value needs converted to the local character set, do that now.
4115 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4117 while (first
!= NULL
) {
4118 if (strcasecmp(name
, first
->pm_name
) == 0) {
4120 return first
->pm_value
;
4121 return getcpy(get_param_value(first
, replace
));
4123 first
= first
->pm_next
;
4130 * Return a parameter value, converting to the local character set if
4134 char *get_param_value(PM pm
, char replace
)
4136 static char buffer
[4096]; /* I hope no parameters are larger */
4137 size_t bufsize
= sizeof(buffer
);
4142 ICONV_CONST
char *p
;
4143 #else /* HAVE_ICONV */
4145 #endif /* HAVE_ICONV */
4150 * If we don't have a character set indicated, it's assumed to be
4151 * US-ASCII. If it matches our character set, we don't need to convert
4155 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4156 strlen(pm
->pm_charset
))) {
4157 return pm
->pm_value
;
4161 * In this case, we need to convert. If we have iconv support, use
4162 * that. Otherwise, go through and simply replace every non-ASCII
4163 * character with the substitution character.
4168 bufsize
= sizeof(buffer
);
4169 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4171 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4172 if (cd
== (iconv_t
) -1) {
4176 inbytes
= strlen(pm
->pm_value
);
4180 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4181 if (errno
!= EILSEQ
) {
4186 * Reset shift state, substitute our character,
4187 * try to restart conversion.
4190 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4203 for (++p
, --inbytes
;
4204 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4223 #endif /* HAVE_ICONV */
4226 * Take everything non-ASCII and substitute the replacement character
4230 bufsize
= sizeof(buffer
);
4231 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4232 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))