1 /* mhparse.c -- routines to parse the contents of MIME messages
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
14 #include <h/mhparse.h>
18 #endif /* HAVE_ICONV */
24 extern int rcachesw
; /* mhcachesbr.c */
25 extern int wcachesw
; /* mhcachesbr.c */
27 int checksw
= 0; /* check Content-MD5 field */
30 * These are for mhfixmsg to:
31 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
33 * 2) Suppress the warning about bogus multipart content, and report it.
34 * 3) Suppress the warning about extraneous trailing ';' in header parameter
37 int skip_mp_cte_check
;
38 int suppress_bogus_mp_content_warning
;
40 int suppress_extraneous_trailing_semicolon_warning
;
43 * By default, suppress warning about multiple MIME-Version header fields.
45 int suppress_multiple_mime_version_warning
= 1;
47 /* list of preferred type/subtype pairs, for -prefer */
48 char *preferred_types
[NPREFS
],
49 *preferred_subtypes
[NPREFS
];
54 * Structures for TEXT messages
56 struct k2v SubText
[] = {
57 { "plain", TEXT_PLAIN
},
58 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
59 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
60 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
63 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
66 * Structures for MULTIPART messages
68 struct k2v SubMultiPart
[] = {
69 { "mixed", MULTI_MIXED
},
70 { "alternative", MULTI_ALTERNATE
},
71 { "digest", MULTI_DIGEST
},
72 { "parallel", MULTI_PARALLEL
},
73 { "related", MULTI_RELATED
},
74 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
78 * Structures for MESSAGE messages
80 struct k2v SubMessage
[] = {
81 { "rfc822", MESSAGE_RFC822
},
82 { "partial", MESSAGE_PARTIAL
},
83 { "external-body", MESSAGE_EXTERNAL
},
84 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
88 * Structure for APPLICATION messages
90 struct k2v SubApplication
[] = {
91 { "octet-stream", APPLICATION_OCTETS
},
92 { "postscript", APPLICATION_POSTSCRIPT
},
93 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
97 * Mapping of names of CTE types in mhbuild directives
99 static struct k2v EncodingType
[] = {
102 { "q-p", CE_QUOTED
},
103 { "quoted-printable", CE_QUOTED
},
104 { "b64", CE_BASE64
},
105 { "base64", CE_BASE64
},
111 int find_cache (CT
, int, int *, char *, char *, int);
115 int type_ok (CT
, int);
116 void content_error (char *, CT
, char *, ...);
119 void free_encoding (CT
, int);
124 static CT
get_content (FILE *, char *, int);
125 static int get_comment (const char *, const char *, char **, char **);
127 static int InitGeneric (CT
);
128 static int InitText (CT
);
129 static int InitMultiPart (CT
);
130 static void reverse_parts (CT
);
131 static void prefer_parts(CT ct
);
132 static int InitMessage (CT
);
133 static int InitApplication (CT
);
134 static int init_encoding (CT
, OpenCEFunc
);
135 static unsigned long size_encoding (CT
);
136 static int InitBase64 (CT
);
137 static int openBase64 (CT
, char **);
138 static int InitQuoted (CT
);
139 static int openQuoted (CT
, char **);
140 static int Init7Bit (CT
);
141 static int openExternal (CT
, CT
, CE
, char **, int *);
142 static int InitFile (CT
);
143 static int openFile (CT
, char **);
144 static int InitFTP (CT
);
145 static int openFTP (CT
, char **);
146 static int InitMail (CT
);
147 static int openMail (CT
, char **);
148 static int readDigest (CT
, char *);
149 static int get_leftover_mp_content (CT
, int);
150 static int InitURL (CT
);
151 static int openURL (CT
, char **);
152 static int parse_header_attrs (const char *, const char *, char **, PM
*,
154 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
155 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
156 static int get_dispo (char *, CT
, int);
158 struct str2init str2cts
[] = {
159 { "application", CT_APPLICATION
, InitApplication
},
160 { "audio", CT_AUDIO
, InitGeneric
},
161 { "image", CT_IMAGE
, InitGeneric
},
162 { "message", CT_MESSAGE
, InitMessage
},
163 { "multipart", CT_MULTIPART
, InitMultiPart
},
164 { "text", CT_TEXT
, InitText
},
165 { "video", CT_VIDEO
, InitGeneric
},
166 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
167 { NULL
, CT_UNKNOWN
, NULL
},
170 struct str2init str2ces
[] = {
171 { "base64", CE_BASE64
, InitBase64
},
172 { "quoted-printable", CE_QUOTED
, InitQuoted
},
173 { "8bit", CE_8BIT
, Init7Bit
},
174 { "7bit", CE_7BIT
, Init7Bit
},
175 { "binary", CE_BINARY
, Init7Bit
},
176 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
177 { NULL
, CE_UNKNOWN
, NULL
},
181 * NOTE WELL: si_key MUST NOT have value of NOTOK
183 * si_key is 1 if access method is anonymous.
185 struct str2init str2methods
[] = {
186 { "afs", 1, InitFile
},
187 { "anon-ftp", 1, InitFTP
},
188 { "ftp", 0, InitFTP
},
189 { "local-file", 0, InitFile
},
190 { "mail-server", 0, InitMail
},
191 { "url", 0, InitURL
},
197 * Main entry point for parsing a MIME message or file.
198 * It returns the Content structure for the top level
199 * entity in the file.
203 parse_mime (char *file
)
212 bogus_mp_content
= 0;
215 * Check if file is actually standard input
217 if ((is_stdin
= !(strcmp (file
, "-")))) {
218 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
220 advise("mhparse", "unable to create temporary file in %s",
224 file
= mh_xstrdup(tfile
);
226 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
227 if (fwrite(buffer
, 1, n
, fp
) != n
) {
228 (void) m_unlink (file
);
229 advise (file
, "error copying to temporary file");
235 if (ferror (stdin
)) {
236 (void) m_unlink (file
);
237 advise ("stdin", "error reading");
241 (void) m_unlink (file
);
242 advise (file
, "error writing");
245 fseek (fp
, 0L, SEEK_SET
);
246 } else if (stat (file
, &statbuf
) == NOTOK
) {
247 advise (file
, "unable to stat");
249 } else if (S_ISDIR(statbuf
.st_mode
)) {
250 /* Don't try to parse a directory. */
251 inform("%s is a directory", file
);
253 } else if ((fp
= fopen (file
, "r")) == NULL
) {
254 advise (file
, "unable to read");
258 if (!(ct
= get_content (fp
, file
, 1))) {
260 (void) m_unlink (file
);
261 inform("unable to decode %s", file
);
266 ct
->c_unlink
= 1; /* temp file to remove */
270 if (ct
->c_end
== 0L) {
271 fseek (fp
, 0L, SEEK_END
);
272 ct
->c_end
= ftell (fp
);
275 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
287 * Main routine for reading/parsing the headers
288 * of a message content.
290 * toplevel = 1 # we are at the top level of the message
291 * toplevel = 0 # we are inside message type or multipart type
292 * # other than multipart/digest
293 * toplevel = -1 # we are inside multipart/digest
294 * NB: on failure we will fclose(in)!
298 get_content (FILE *in
, char *file
, int toplevel
)
301 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
305 m_getfld_state_t gstate
= 0;
307 /* allocate the content structure */
310 ct
->c_file
= add (file
, NULL
);
311 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
314 * Parse the header fields for this
315 * content into a linked list.
317 m_getfld_track_filepos (&gstate
, in
);
318 for (compnum
= 1;;) {
319 int bufsz
= sizeof buf
;
320 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
325 /* get copies of the buffers */
326 np
= mh_xstrdup(name
);
327 vp
= mh_xstrdup(buf
);
329 /* if necessary, get rest of field */
330 while (state
== FLDPLUS
) {
332 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
333 vp
= add (buf
, vp
); /* add to previous value */
336 /* Now add the header data to the list */
337 add_header (ct
, np
, vp
);
339 /* continue, to see if this isn't the last header field */
340 ct
->c_begin
= ftell (in
) + 1;
344 if (name
[0] == ':') {
345 /* Special case: no blank line between header and body. The
346 file position indicator is on the newline at the end of the
347 line, but it needs to be one prior to the beginning of the
348 line. So subtract the length of the line, bufsz, plus 1. */
349 ct
->c_begin
= ftell (in
) - (bufsz
+ 1);
351 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
356 ct
->c_begin
= ftell (in
);
361 adios (NULL
, "message format error in component #%d", compnum
);
364 adios (NULL
, "getfld() returned %d", state
);
367 /* break out of the loop */
370 m_getfld_state_destroy (&gstate
);
373 * Read the content headers. We will parse the
374 * MIME related header fields into their various
375 * structures and set internal flags related to
376 * content type/subtype, etc.
379 hp
= ct
->c_first_hf
; /* start at first header field */
381 /* Get MIME-Version field */
382 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
387 vrsn
= add (hp
->value
, NULL
);
389 /* Now, cleanup this field */
392 while (isspace ((unsigned char) *cp
))
394 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
396 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
397 if (!isspace ((unsigned char) *dp
))
401 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
404 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
407 for (dp
= cp
; istoken (*dp
); dp
++)
411 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
414 inform("message %s has unknown value for %s: field (%s), continuing...",
415 ct
->c_file
, VRSN_FIELD
, cp
);
420 if (! suppress_multiple_mime_version_warning
)
421 inform("message %s has multiple %s: fields",
422 ct
->c_file
, VRSN_FIELD
);
426 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
427 /* Get Content-Type field */
428 struct str2init
*s2i
;
429 CI ci
= &ct
->c_ctinfo
;
431 /* Check if we've already seen a Content-Type header */
433 inform("message %s has multiple %s: fields",
434 ct
->c_file
, TYPE_FIELD
);
438 /* Parse the Content-Type field */
439 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
443 * Set the Init function and the internal
444 * flag for this content type.
446 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
447 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
449 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
451 ct
->c_type
= s2i
->si_val
;
452 ct
->c_ctinitfnx
= s2i
->si_init
;
454 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
455 /* Get Content-Transfer-Encoding field */
457 struct str2init
*s2i
;
460 * Check if we've already seen the
461 * Content-Transfer-Encoding field
464 inform("message %s has multiple %s: fields",
465 ct
->c_file
, ENCODING_FIELD
);
469 /* get copy of this field */
470 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
472 while (isspace ((unsigned char) *cp
))
474 for (dp
= cp
; istoken (*dp
); dp
++)
480 * Find the internal flag and Init function
481 * for this transfer encoding.
483 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
484 if (!strcasecmp (cp
, s2i
->si_key
))
486 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
489 ct
->c_encoding
= s2i
->si_val
;
491 /* Call the Init function for this encoding */
492 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
495 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
496 /* Get Content-MD5 field */
502 if (ct
->c_digested
) {
503 inform("message %s has multiple %s: fields",
504 ct
->c_file
, MD5_FIELD
);
508 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
510 while (isspace ((unsigned char) *cp
))
512 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
514 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
515 if (!isspace ((unsigned char) *dp
))
519 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
522 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
527 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
535 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
536 /* Get Content-ID field */
537 ct
->c_id
= add (hp
->value
, ct
->c_id
);
539 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
540 /* Get Content-Description field */
541 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
543 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
544 /* Get Content-Disposition field */
545 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
550 hp
= hp
->next
; /* next header field */
554 * Check if we saw a Content-Type field.
555 * If not, then assign a default value for
556 * it, and the Init function.
560 * If we are inside a multipart/digest message,
561 * so default type is message/rfc822
564 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
566 ct
->c_type
= CT_MESSAGE
;
567 ct
->c_ctinitfnx
= InitMessage
;
570 * Else default type is text/plain
572 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
574 ct
->c_type
= CT_TEXT
;
575 ct
->c_ctinitfnx
= InitText
;
579 /* Use default Transfer-Encoding, if necessary */
581 ct
->c_encoding
= CE_7BIT
;
594 * small routine to add header field to list
598 add_header (CT ct
, char *name
, char *value
)
602 /* allocate header field structure */
605 /* link data into header structure */
610 /* link header structure into the list */
611 if (ct
->c_first_hf
== NULL
) {
612 ct
->c_first_hf
= hp
; /* this is the first */
615 ct
->c_last_hf
->next
= hp
; /* add it to the end */
624 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
625 * directives. Fills in the information of the CTinfo structure.
628 get_ctinfo (char *cp
, CT ct
, int magic
)
637 /* store copy of Content-Type line */
638 cp
= ct
->c_ctline
= add (cp
, NULL
);
640 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
643 /* change newlines to spaces */
644 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
647 /* trim trailing spaces */
648 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
649 if (!isspace ((unsigned char) *dp
))
654 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
656 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
657 &ci
->ci_comment
) == NOTOK
)
660 for (dp
= cp
; istoken (*dp
); dp
++)
664 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
++)
700 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
704 if (!*ci
->ci_subtype
) {
705 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
706 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
709 to_lower(ci
->ci_subtype
);
712 while (isspace ((unsigned char) *cp
))
715 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
716 &ci
->ci_comment
) == NOTOK
)
719 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
720 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
721 &ci
->ci_comment
)) != OK
) {
722 return status
== NOTOK
? NOTOK
: OK
;
726 * Get any <Content-Id> given in buffer
728 if (magic
&& *cp
== '<') {
731 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
732 inform("invalid ID in message %s", ct
->c_file
);
738 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
744 while (isspace ((unsigned char) *cp
))
749 * Get any [Content-Description] given in buffer.
751 if (magic
&& *cp
== '[') {
753 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
757 inform("invalid description in message %s", ct
->c_file
);
765 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
771 while (isspace ((unsigned char) *cp
))
776 * Get any {Content-Disposition} given in buffer.
778 if (magic
&& *cp
== '{') {
780 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
784 inform("invalid disposition in message %s", ct
->c_file
);
792 if (get_dispo(cp
, ct
, 1) != OK
)
798 while (isspace ((unsigned char) *cp
))
803 * Get any extension directives (right now just the content transfer
804 * encoding, but maybe others) that we care about.
807 if (magic
&& *cp
== '*') {
809 * See if it's a CTE we match on
814 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
818 inform("invalid null transfer encoding specification");
825 ct
->c_reqencoding
= CE_UNKNOWN
;
827 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
828 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
829 ct
->c_reqencoding
= kv
->kv_value
;
834 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
835 inform("invalid CTE specification: \"%s\"", dp
);
839 while (isspace ((unsigned char) *cp
))
844 * Check if anything is left over
848 ci
->ci_magic
= mh_xstrdup(cp
);
850 /* If there is a Content-Disposition header and it doesn't
851 have a *filename=, extract it from the magic contents.
852 The r1bindex call skips any leading directory
854 if (ct
->c_dispo_type
&&
855 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
856 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
857 r1bindex(ci
->ci_magic
, '/'), 0);
861 inform("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
++)
915 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
919 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
922 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
923 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
925 if (status
== NOTOK
) {
930 inform("extraneous information in message %s's %s: field\n%*s(%s)",
931 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
937 ct
->c_dispo
= dispoheader
;
944 get_comment (const char *filename
, const char *fieldname
, char **ap
,
949 char c
, buffer
[BUFSIZ
], *dp
;
959 inform("invalid comment in message %s's %s: field",
960 filename
, fieldname
);
965 if ((c
= *cp
++) == '\0')
988 if ((dp
= *commentp
)) {
989 *commentp
= concat (dp
, " ", buffer
, NULL
);
992 *commentp
= mh_xstrdup(buffer
);
996 while (isspace ((unsigned char) *cp
))
1007 * Handles content types audio, image, and video.
1008 * There's not much to do right here.
1016 return OK
; /* not much to do here */
1027 char buffer
[BUFSIZ
];
1032 CI ci
= &ct
->c_ctinfo
;
1034 /* check for missing subtype */
1035 if (!*ci
->ci_subtype
)
1036 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1039 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1041 /* allocate text character set structure */
1043 ct
->c_ctparams
= (void *) t
;
1045 /* scan for charset parameter */
1046 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1047 if (!strcasecmp (pm
->pm_name
, "charset"))
1050 /* check if content specified a character set */
1052 chset
= pm
->pm_value
;
1053 t
->tx_charset
= CHARSET_SPECIFIED
;
1055 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1059 * If we can not handle character set natively,
1060 * then check profile for string to modify the
1061 * terminal or display method.
1063 * termproc is for mhshow, though mhlist -debug prints it, too.
1065 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1066 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1067 if ((cp
= context_find (buffer
)))
1068 ct
->c_termproc
= mh_xstrdup(cp
);
1080 InitMultiPart (CT ct
)
1090 struct multipart
*m
;
1091 struct part
*part
, **next
;
1092 CI ci
= &ct
->c_ctinfo
;
1097 * The encoding for multipart messages must be either
1098 * 7bit, 8bit, or binary (per RFC 2045).
1100 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1101 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1102 /* Copy the Content-Transfer-Encoding header field body so we can
1103 remove any trailing whitespace and leading blanks from it. */
1104 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1106 bp
= cte
+ strlen (cte
) - 1;
1107 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1108 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1110 inform("\"%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, continuing...",
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 */
1138 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1139 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1143 /* allocate primary structure for multipart info */
1145 ct
->c_ctparams
= (void *) m
;
1147 /* check if boundary parameter contains only whitespace characters */
1148 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1151 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1152 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1156 /* remove trailing whitespace from boundary parameter */
1157 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1158 if (!isspace ((unsigned char) *dp
))
1162 /* record boundary separators */
1163 m
->mp_start
= concat (bp
, "\n", NULL
);
1164 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1166 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1167 advise (ct
->c_file
, "unable to open for reading");
1171 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1173 next
= &m
->mp_parts
;
1177 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1182 if (bufp
[0] != '-' || bufp
[1] != '-')
1185 if (strcmp (bufp
+ 2, m
->mp_start
))
1190 next
= &part
->mp_next
;
1192 if (!(p
= get_content (fp
, ct
->c_file
,
1193 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1201 fseek (fp
, pos
, SEEK_SET
);
1204 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1208 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1209 if (p
->c_end
< p
->c_begin
)
1210 p
->c_begin
= p
->c_end
;
1215 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1220 if (! suppress_bogus_mp_content_warning
) {
1221 inform("bogus multipart content in message %s", ct
->c_file
);
1223 bogus_mp_content
= 1;
1225 if (!inout
&& part
) {
1227 p
->c_end
= ct
->c_end
;
1229 if (p
->c_begin
>= p
->c_end
) {
1230 for (next
= &m
->mp_parts
; *next
!= part
;
1231 next
= &((*next
)->mp_next
))
1240 /* reverse the order of the parts for multipart/alternative */
1241 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1247 * label all subparts with part number, and
1248 * then initialize the content of the subpart.
1253 char partnam
[BUFSIZ
];
1256 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1257 pp
= partnam
+ strlen (partnam
);
1262 for (part
= m
->mp_parts
, partnum
= 1; part
;
1263 part
= part
->mp_next
, partnum
++) {
1266 sprintf (pp
, "%d", partnum
);
1267 p
->c_partno
= mh_xstrdup(partnam
);
1269 /* initialize the content of the subparts */
1270 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1279 get_leftover_mp_content (ct
, 1);
1280 get_leftover_mp_content (ct
, 0);
1290 * reverse the order of the parts of a multipart/alternative,
1291 * presumably to put the "most favored" alternative first, for
1292 * ease of choosing/displaying it later on. from a mail message on
1293 * nmh-workers, from kenh:
1294 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1295 * see code in mhn that did the same thing... According to the RCS
1296 * logs, that code was around from the initial checkin of mhn.c by
1297 * John Romine in 1992, which is as far back as we have."
1300 reverse_parts (CT ct
)
1302 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1306 /* Reverse the order of its parts by walking the mp_parts list
1307 and pushing each node to the front. */
1308 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1309 next
= part
->mp_next
;
1310 part
->mp_next
= m
->mp_parts
;
1316 move_preferred_part (CT ct
, char *type
, char *subtype
)
1318 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1319 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1323 /* move the matching part(s) to the head of the list: walk the
1324 * list of parts, move matching parts to a new list (maintaining
1325 * their order), and finally, concatenate the old list onto the
1332 head
->mp_next
= m
->mp_parts
;
1333 nhead
->mp_next
= NULL
;
1337 part
= head
->mp_next
;
1338 while (part
!= NULL
) {
1339 ci
= &part
->mp_part
->c_ctinfo
;
1340 if (!strcasecmp(ci
->ci_type
, type
) &&
1341 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1342 prev
->mp_next
= part
->mp_next
;
1343 part
->mp_next
= NULL
;
1344 ntail
->mp_next
= part
;
1346 part
= prev
->mp_next
;
1349 part
= prev
->mp_next
;
1352 ntail
->mp_next
= head
->mp_next
;
1353 m
->mp_parts
= nhead
->mp_next
;
1358 * move parts that match the user's preferences (-prefer) to the head
1359 * of the line. process preferences in reverse so first one given
1360 * ends up first in line
1366 for (i
= npreferred
-1; i
>= 0; i
--)
1367 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1372 /* parse_mime() arranges alternates in reverse (priority) order. This
1373 function can be used to reverse them back. This will put, for
1374 example, a text/plain part before a text/html part in a
1375 multipart/alternative part, for example, where it belongs. */
1377 reverse_alternative_parts (CT ct
) {
1378 if (ct
->c_type
== CT_MULTIPART
) {
1379 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1382 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1386 /* And call recursively on each part of a multipart. */
1387 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1388 reverse_alternative_parts (part
->mp_part
);
1401 CI ci
= &ct
->c_ctinfo
;
1403 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1404 inform("\"%s/%s\" type in message %s should be encoded in "
1405 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1410 /* check for missing subtype */
1411 if (!*ci
->ci_subtype
)
1412 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1415 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1417 switch (ct
->c_subtype
) {
1418 case MESSAGE_RFC822
:
1421 case MESSAGE_PARTIAL
:
1427 ct
->c_ctparams
= (void *) p
;
1429 /* scan for parameters "id", "number", and "total" */
1430 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1431 if (!strcasecmp (pm
->pm_name
, "id")) {
1432 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1435 if (!strcasecmp (pm
->pm_name
, "number")) {
1436 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1437 || p
->pm_partno
< 1) {
1439 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1440 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1441 ct
->c_file
, TYPE_FIELD
);
1446 if (!strcasecmp (pm
->pm_name
, "total")) {
1447 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1456 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1457 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1458 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1464 case MESSAGE_EXTERNAL
:
1472 ct
->c_ctparams
= (void *) e
;
1475 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1476 advise (ct
->c_file
, "unable to open for reading");
1480 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1482 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1490 p
->c_ceopenfnx
= NULL
;
1491 if ((exresult
= params_external (ct
, 0)) != NOTOK
1492 && p
->c_ceopenfnx
== openMail
) {
1496 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1498 content_error (NULL
, ct
,
1499 "empty body for access-type=mail-server");
1503 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1504 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1506 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1508 adios ("failed", "fread");
1511 adios (NULL
, "unexpected EOF from fread");
1514 bp
+= cc
, size
-= cc
;
1521 p
->c_end
= p
->c_begin
;
1526 if (exresult
== NOTOK
)
1528 if (e
->eb_flags
== NOTOK
)
1531 switch (p
->c_type
) {
1536 if (p
->c_subtype
!= MESSAGE_RFC822
)
1540 e
->eb_partno
= ct
->c_partno
;
1542 (*p
->c_ctinitfnx
) (p
);
1557 params_external (CT ct
, int composing
)
1560 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1561 CI ci
= &ct
->c_ctinfo
;
1563 ct
->c_ceopenfnx
= NULL
;
1564 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1565 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1566 struct str2init
*s2i
;
1567 CT p
= e
->eb_content
;
1569 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1570 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1573 e
->eb_access
= pm
->pm_value
;
1574 e
->eb_flags
= NOTOK
;
1575 p
->c_encoding
= CE_EXTERNAL
;
1578 e
->eb_access
= s2i
->si_key
;
1579 e
->eb_flags
= s2i
->si_val
;
1580 p
->c_encoding
= CE_EXTERNAL
;
1582 /* Call the Init function for this external type */
1583 if ((*s2i
->si_init
)(p
) == NOTOK
)
1587 if (!strcasecmp (pm
->pm_name
, "name")) {
1588 e
->eb_name
= pm
->pm_value
;
1591 if (!strcasecmp (pm
->pm_name
, "permission")) {
1592 e
->eb_permission
= pm
->pm_value
;
1595 if (!strcasecmp (pm
->pm_name
, "site")) {
1596 e
->eb_site
= pm
->pm_value
;
1599 if (!strcasecmp (pm
->pm_name
, "directory")) {
1600 e
->eb_dir
= pm
->pm_value
;
1603 if (!strcasecmp (pm
->pm_name
, "mode")) {
1604 e
->eb_mode
= pm
->pm_value
;
1607 if (!strcasecmp (pm
->pm_name
, "size")) {
1608 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1611 if (!strcasecmp (pm
->pm_name
, "server")) {
1612 e
->eb_server
= pm
->pm_value
;
1615 if (!strcasecmp (pm
->pm_name
, "subject")) {
1616 e
->eb_subject
= pm
->pm_value
;
1619 if (!strcasecmp (pm
->pm_name
, "url")) {
1621 * According to RFC 2017, we have to remove all whitespace from
1625 char *u
, *p
= pm
->pm_value
;
1626 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1628 for (; *p
!= '\0'; p
++) {
1629 if (! isspace((unsigned char) *p
))
1636 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1637 e
->eb_body
= getcpy (pm
->pm_value
);
1642 if (!e
->eb_access
) {
1643 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1644 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1657 InitApplication (CT ct
)
1659 CI ci
= &ct
->c_ctinfo
;
1662 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1669 * TRANSFER ENCODINGS
1673 init_encoding (CT ct
, OpenCEFunc openfnx
)
1675 ct
->c_ceopenfnx
= openfnx
;
1676 ct
->c_ceclosefnx
= close_encoding
;
1677 ct
->c_cesizefnx
= size_encoding
;
1684 close_encoding (CT ct
)
1686 CE ce
= &ct
->c_cefile
;
1695 static unsigned long
1696 size_encoding (CT ct
)
1701 CE ce
= &ct
->c_cefile
;
1704 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1705 return (long) st
.st_size
;
1708 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1709 return (long) st
.st_size
;
1713 if (ct
->c_encoding
== CE_EXTERNAL
)
1714 return (ct
->c_end
- ct
->c_begin
);
1717 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1718 return (ct
->c_end
- ct
->c_begin
);
1720 if (fstat (fd
, &st
) != NOTOK
)
1721 size
= (long) st
.st_size
;
1725 (*ct
->c_ceclosefnx
) (ct
);
1737 return init_encoding (ct
, openBase64
);
1742 openBase64 (CT ct
, char **file
)
1745 int fd
, own_ct_fp
= 0;
1746 char *cp
, *buffer
= NULL
;
1747 /* sbeck -- handle suffixes */
1749 CE ce
= &ct
->c_cefile
;
1750 unsigned char *decoded
;
1752 unsigned char digest
[16];
1755 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1760 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1761 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1767 if (*file
== NULL
) {
1770 ce
->ce_file
= mh_xstrdup(*file
);
1774 /* sbeck@cise.ufl.edu -- handle suffixes */
1776 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1777 if (ce
->ce_unlink
) {
1778 /* Create temporary file with filename extension. */
1779 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1780 adios(NULL
, "unable to create temporary file in %s",
1784 ce
->ce_file
= add (cp
, ce
->ce_file
);
1786 } else if (*file
== NULL
) {
1788 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1789 adios(NULL
, "unable to create temporary file in %s",
1792 ce
->ce_file
= mh_xstrdup(tempfile
);
1795 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1796 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1800 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1801 adios (NULL
, "internal error(1)");
1803 buffer
= mh_xmalloc (len
+ 1);
1806 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1807 content_error (ct
->c_file
, ct
, "unable to open for reading");
1813 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1816 switch (cc
= read (fd
, cp
, len
)) {
1818 content_error (ct
->c_file
, ct
, "error reading from");
1822 content_error (NULL
, ct
, "premature eof");
1833 /* decodeBase64() requires null-terminated input. */
1836 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1837 ct
->c_digested
? digest
: NULL
) == OK
) {
1839 unsigned char *decoded_p
= decoded
;
1840 for (i
= 0; i
< decoded_len
; ++i
) {
1841 putc (*decoded_p
++, ce
->ce_fp
);
1844 if (ferror (ce
->ce_fp
)) {
1845 content_error (ce
->ce_file
, ct
, "error writing to");
1849 if (ct
->c_digested
) {
1850 if (memcmp(digest
, ct
->c_digest
,
1851 sizeof(digest
) / sizeof(digest
[0]))) {
1852 content_error (NULL
, ct
,
1853 "content integrity suspect (digest mismatch) -- continuing");
1856 fprintf (stderr
, "content integrity confirmed\n");
1864 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1866 if (fflush (ce
->ce_fp
)) {
1867 content_error (ce
->ce_file
, ct
, "error writing to");
1871 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1874 *file
= ce
->ce_file
;
1880 return fileno (ce
->ce_fp
);
1887 free_encoding (ct
, 0);
1897 static char hex2nib
[0x80] = {
1898 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1899 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1900 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1901 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1905 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1909 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1910 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1911 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1913 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1920 return init_encoding (ct
, openQuoted
);
1925 openQuoted (CT ct
, char **file
)
1927 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1933 CE ce
= &ct
->c_cefile
;
1934 /* sbeck -- handle suffixes */
1939 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1944 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1945 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1951 if (*file
== NULL
) {
1954 ce
->ce_file
= mh_xstrdup(*file
);
1958 /* sbeck@cise.ufl.edu -- handle suffixes */
1960 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1961 if (ce
->ce_unlink
) {
1962 /* Create temporary file with filename extension. */
1963 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1964 adios(NULL
, "unable to create temporary file in %s",
1968 ce
->ce_file
= add (cp
, ce
->ce_file
);
1970 } else if (*file
== NULL
) {
1972 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1973 adios(NULL
, "unable to create temporary file in %s",
1976 ce
->ce_file
= mh_xstrdup(tempfile
);
1979 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1980 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1984 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1985 adios (NULL
, "internal error(2)");
1988 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1989 content_error (ct
->c_file
, ct
, "unable to open for reading");
1995 if ((digested
= ct
->c_digested
))
1996 MD5Init (&mdContext
);
2003 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2005 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2006 content_error (NULL
, ct
, "premature eof");
2010 if ((cc
= gotlen
) > len
)
2014 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2015 if (!isspace ((unsigned char) *ep
))
2020 for (; cp
< ep
; cp
++) {
2022 /* in an escape sequence */
2024 /* at byte 1 of an escape sequence */
2025 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2026 /* next is byte 2 */
2029 /* at byte 2 of an escape sequence */
2031 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2032 putc (mask
, ce
->ce_fp
);
2034 MD5Update (&mdContext
, &mask
, 1);
2035 if (ferror (ce
->ce_fp
)) {
2036 content_error (ce
->ce_file
, ct
, "error writing to");
2039 /* finished escape sequence; next may be literal or a new
2040 * escape sequence */
2043 /* on to next byte */
2047 /* not in an escape sequence */
2049 /* starting an escape sequence, or invalid '='? */
2050 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2051 /* "=\n" soft line break, eat the \n */
2055 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2056 /* We don't have 2 bytes left, so this is an invalid
2057 * escape sequence; just show the raw bytes (below). */
2058 } else if (isxdigit ((unsigned char) cp
[1]) &&
2059 isxdigit ((unsigned char) cp
[2])) {
2060 /* Next 2 bytes are hex digits, making this a valid escape
2061 * sequence; let's decode it (above). */
2065 /* One or both of the next 2 is out of range, making this
2066 * an invalid escape sequence; just show the raw bytes
2070 /* Just show the raw byte. */
2071 putc (*cp
, ce
->ce_fp
);
2074 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2076 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2079 if (ferror (ce
->ce_fp
)) {
2080 content_error (ce
->ce_file
, ct
, "error writing to");
2086 content_error (NULL
, ct
,
2087 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2091 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2093 if (fflush (ce
->ce_fp
)) {
2094 content_error (ce
->ce_file
, ct
, "error writing to");
2099 unsigned char digest
[16];
2101 MD5Final (digest
, &mdContext
);
2102 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2103 sizeof(digest
) / sizeof(digest
[0])))
2104 content_error (NULL
, ct
,
2105 "content integrity suspect (digest mismatch) -- continuing");
2108 fprintf (stderr
, "content integrity confirmed\n");
2111 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2114 *file
= ce
->ce_file
;
2120 return fileno (ce
->ce_fp
);
2123 free_encoding (ct
, 0);
2140 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2143 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2149 open7Bit (CT ct
, char **file
)
2151 int cc
, fd
, len
, own_ct_fp
= 0;
2152 char buffer
[BUFSIZ
];
2153 /* sbeck -- handle suffixes */
2156 CE ce
= &ct
->c_cefile
;
2159 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2164 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2165 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2171 if (*file
== NULL
) {
2174 ce
->ce_file
= mh_xstrdup(*file
);
2178 /* sbeck@cise.ufl.edu -- handle suffixes */
2180 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2181 if (ce
->ce_unlink
) {
2182 /* Create temporary file with filename extension. */
2183 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2184 adios(NULL
, "unable to create temporary file in %s",
2188 ce
->ce_file
= add (cp
, ce
->ce_file
);
2190 } else if (*file
== NULL
) {
2192 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2193 adios(NULL
, "unable to create temporary file in %s",
2196 ce
->ce_file
= mh_xstrdup(tempfile
);
2199 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2200 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2204 if (ct
->c_type
== CT_MULTIPART
) {
2205 CI ci
= &ct
->c_ctinfo
;
2209 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2210 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2211 + 1 + strlen (ci
->ci_subtype
);
2212 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2215 fputs (buffer
, ce
->ce_fp
);
2219 if (ci
->ci_comment
) {
2220 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2221 fputs ("\n\t", ce
->ce_fp
);
2225 putc (' ', ce
->ce_fp
);
2228 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2231 fprintf (ce
->ce_fp
, "\n");
2233 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2235 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2237 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2238 fprintf (ce
->ce_fp
, "\n");
2241 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2242 adios (NULL
, "internal error(3)");
2245 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2246 content_error (ct
->c_file
, ct
, "unable to open for reading");
2252 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2254 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2256 content_error (ct
->c_file
, ct
, "error reading from");
2260 content_error (NULL
, ct
, "premature eof");
2268 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2269 advise ("open7Bit", "fwrite");
2271 if (ferror (ce
->ce_fp
)) {
2272 content_error (ce
->ce_file
, ct
, "error writing to");
2277 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2279 if (fflush (ce
->ce_fp
)) {
2280 content_error (ce
->ce_file
, ct
, "error writing to");
2284 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2287 *file
= ce
->ce_file
;
2292 return fileno (ce
->ce_fp
);
2295 free_encoding (ct
, 0);
2309 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2311 char cachefile
[BUFSIZ
];
2314 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2319 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2320 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2326 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2327 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2328 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2329 ce
->ce_file
= mh_xstrdup(cachefile
);
2333 admonish (cachefile
, "unable to fopen for reading");
2336 *fd
= fileno (ce
->ce_fp
);
2340 *file
= ce
->ce_file
;
2341 *fd
= fileno (ce
->ce_fp
);
2352 return init_encoding (ct
, openFile
);
2357 openFile (CT ct
, char **file
)
2360 char cachefile
[BUFSIZ
];
2361 struct exbody
*e
= ct
->c_ctexbody
;
2362 CE ce
= &ct
->c_cefile
;
2364 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2376 content_error (NULL
, ct
, "missing name parameter");
2380 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2383 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2384 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2388 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2389 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2390 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2394 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2395 if ((fp
= fopen (cachefile
, "w"))) {
2397 char buffer
[BUFSIZ
];
2398 FILE *gp
= ce
->ce_fp
;
2400 fseek (gp
, 0L, SEEK_SET
);
2402 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2404 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2405 advise ("openFile", "fwrite");
2410 admonish (ce
->ce_file
, "error reading");
2411 (void) m_unlink (cachefile
);
2415 admonish (cachefile
, "error writing");
2416 (void) m_unlink (cachefile
);
2423 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2424 *file
= ce
->ce_file
;
2425 return fileno (ce
->ce_fp
);
2435 return init_encoding (ct
, openFTP
);
2440 openFTP (CT ct
, char **file
)
2442 int cachetype
, caching
, fd
;
2444 char *bp
, *ftp
, *user
, *pass
;
2445 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2447 CE ce
= &ct
->c_cefile
;
2448 static char *username
= NULL
;
2449 static char *password
= NULL
;
2453 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2459 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2470 if (!e
->eb_name
|| !e
->eb_site
) {
2471 content_error (NULL
, ct
, "missing %s parameter",
2472 e
->eb_name
? "site": "name");
2476 /* Get the buffer ready to go */
2478 buflen
= sizeof(buffer
);
2481 * Construct the query message for user
2483 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2489 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2495 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2496 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2501 if (e
->eb_size
> 0) {
2502 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2507 snprintf (bp
, buflen
, "? ");
2510 * Now, check the answer
2512 if (!read_yes_or_no_if_tty (buffer
))
2517 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2521 ruserpass (e
->eb_site
, &username
, &password
, 0);
2526 ce
->ce_unlink
= (*file
== NULL
);
2528 cachefile
[0] = '\0';
2529 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2530 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2531 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2532 if (*file
== NULL
) {
2539 ce
->ce_file
= mh_xstrdup(*file
);
2541 ce
->ce_file
= mh_xstrdup(cachefile
);
2544 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2545 adios(NULL
, "unable to create temporary file in %s",
2548 ce
->ce_file
= mh_xstrdup(tempfile
);
2551 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2552 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2557 int child_id
, i
, vecp
;
2561 vec
[vecp
++] = r1bindex (ftp
, '/');
2562 vec
[vecp
++] = e
->eb_site
;
2565 vec
[vecp
++] = e
->eb_dir
;
2566 vec
[vecp
++] = e
->eb_name
;
2567 vec
[vecp
++] = ce
->ce_file
,
2568 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2569 ? "ascii" : "binary";
2574 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2578 adios ("fork", "unable to");
2582 close (fileno (ce
->ce_fp
));
2584 fprintf (stderr
, "unable to exec ");
2590 if (pidXwait (child_id
, NULL
)) {
2591 username
= password
= NULL
;
2601 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2606 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2607 if ((fp
= fopen (cachefile
, "w"))) {
2609 FILE *gp
= ce
->ce_fp
;
2611 fseek (gp
, 0L, SEEK_SET
);
2613 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2615 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2616 advise ("openFTP", "fwrite");
2621 admonish (ce
->ce_file
, "error reading");
2622 (void) m_unlink (cachefile
);
2626 admonish (cachefile
, "error writing");
2627 (void) m_unlink (cachefile
);
2635 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2636 *file
= ce
->ce_file
;
2637 return fileno (ce
->ce_fp
);
2648 return init_encoding (ct
, openMail
);
2653 openMail (CT ct
, char **file
)
2655 int child_id
, fd
, i
, vecp
;
2657 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2658 struct exbody
*e
= ct
->c_ctexbody
;
2659 CE ce
= &ct
->c_cefile
;
2661 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2672 if (!e
->eb_server
) {
2673 content_error (NULL
, ct
, "missing server parameter");
2677 /* Get buffer ready to go */
2679 buflen
= sizeof(buffer
);
2681 /* Now, construct query message */
2682 snprintf (bp
, buflen
, "Retrieve content");
2688 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2694 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2696 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2698 /* Now, check answer */
2699 if (!read_yes_or_no_if_tty (buffer
))
2703 vec
[vecp
++] = r1bindex (mailproc
, '/');
2704 vec
[vecp
++] = e
->eb_server
;
2705 vec
[vecp
++] = "-subject";
2706 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2707 vec
[vecp
++] = "-body";
2708 vec
[vecp
++] = e
->eb_body
;
2711 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2715 advise ("fork", "unable to");
2719 execvp (mailproc
, vec
);
2720 fprintf (stderr
, "unable to exec ");
2726 if (pidXwait (child_id
, NULL
) == OK
)
2727 inform("request sent");
2731 if (*file
== NULL
) {
2733 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2734 adios(NULL
, "unable to create temporary file in %s",
2737 ce
->ce_file
= mh_xstrdup(tempfile
);
2740 ce
->ce_file
= mh_xstrdup(*file
);
2744 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2745 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2749 /* showproc is for mhshow and mhstore, though mhlist -debug
2750 * prints it, too. */
2751 mh_xfree(ct
->c_showproc
);
2752 ct
->c_showproc
= mh_xstrdup("true");
2754 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2755 *file
= ce
->ce_file
;
2756 return fileno (ce
->ce_fp
);
2767 return init_encoding (ct
, openURL
);
2772 openURL (CT ct
, char **file
)
2774 struct exbody
*e
= ct
->c_ctexbody
;
2775 CE ce
= &ct
->c_cefile
;
2776 char *urlprog
, *program
;
2777 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2778 int fd
, caching
, cachetype
;
2779 struct msgs_array args
= { 0, 0, NULL
};
2782 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2786 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2790 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2802 content_error(NULL
, ct
, "missing url parameter");
2806 ce
->ce_unlink
= (*file
== NULL
);
2808 cachefile
[0] = '\0';
2810 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2811 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2812 if (*file
== NULL
) {
2819 ce
->ce_file
= mh_xstrdup(*file
);
2821 ce
->ce_file
= mh_xstrdup(cachefile
);
2824 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2825 adios(NULL
, "unable to create temporary file in %s",
2828 ce
->ce_file
= mh_xstrdup(tempfile
);
2831 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2832 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2836 switch (child_id
= fork()) {
2838 adios ("fork", "unable to");
2842 argsplit_msgarg(&args
, urlprog
, &program
);
2843 app_msgarg(&args
, e
->eb_url
);
2844 app_msgarg(&args
, NULL
);
2845 dup2(fileno(ce
->ce_fp
), 1);
2846 close(fileno(ce
->ce_fp
));
2847 execvp(program
, args
.msgs
);
2848 fprintf(stderr
, "Unable to exec ");
2854 if (pidXwait(child_id
, NULL
)) {
2862 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2867 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2868 if ((fp
= fopen(cachefile
, "w"))) {
2870 FILE *gp
= ce
->ce_fp
;
2872 fseeko(gp
, 0, SEEK_SET
);
2874 while ((cc
= fread(buffer
, sizeof(*buffer
),
2875 sizeof(buffer
), gp
)) > 0)
2876 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2877 advise ("openURL", "fwrite");
2883 admonish(ce
->ce_file
, "error reading");
2884 (void) m_unlink (cachefile
);
2891 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2892 *file
= ce
->ce_file
;
2898 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2899 * has to be base64 decoded.
2902 readDigest (CT ct
, char *cp
)
2904 unsigned char *digest
;
2907 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2908 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2910 if (strlen ((char *) digest
) <= maxlen
) {
2911 memcpy (ct
->c_digest
, digest
, maxlen
);
2916 fprintf (stderr
, "MD5 digest=");
2917 for (i
= 0; i
< maxlen
; ++i
) {
2918 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2920 fprintf (stderr
, "\n");
2926 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2927 (int) strlen ((char *) digest
));
2937 /* Multipart parts might have content before the first subpart and/or
2938 after the last subpart that hasn't been stored anywhere else, so do
2941 get_leftover_mp_content (CT ct
, int before
/* or after */)
2943 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2945 int found_boundary
= 0;
2951 char *content
= NULL
;
2953 if (! m
) return NOTOK
;
2956 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2958 /* Isolate the beginning of this part to the beginning of the
2959 first subpart and save any content between them. */
2960 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2961 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2962 boundary
= concat ("--", m
->mp_start
, NULL
);
2964 struct part
*last_subpart
= NULL
;
2965 struct part
*subpart
;
2967 /* Go to the last subpart to get its end position. */
2968 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2969 last_subpart
= subpart
;
2972 if (last_subpart
== NULL
) return NOTOK
;
2974 /* Isolate the end of the last subpart to the end of this part
2975 and save any content between them. */
2976 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2977 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2978 boundary
= concat ("--", m
->mp_stop
, NULL
);
2981 /* Back up by 1 to pick up the newline. */
2982 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2984 /* Don't look beyond beginning of first subpart (before) or
2985 next part (after). */
2986 if (read
> max
) bufp
[read
-max
] = '\0';
2989 if (! strcmp (bufp
, boundary
)) {
2993 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2999 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3001 char *old_content
= content
;
3002 content
= concat (content
, bufp
, NULL
);
3006 ? concat ("\n", bufp
, NULL
)
3007 : concat (bufp
, NULL
);
3012 if (found_boundary
|| read
> max
) break;
3014 if (read
> max
) break;
3018 /* Skip the newline if that's all there is. */
3022 /* Remove trailing newline, except at EOF. */
3023 if ((before
|| ! feof (ct
->c_fp
)) &&
3024 (cp
= content
+ strlen (content
)) > content
&&
3029 if (strlen (content
) > 1) {
3031 m
->mp_content_before
= content
;
3033 m
->mp_content_after
= content
;
3048 ct_type_str (int type
) {
3050 case CT_APPLICATION
:
3051 return "application";
3067 return "unknown_type";
3073 ct_subtype_str (int type
, int subtype
) {
3075 case CT_APPLICATION
:
3077 case APPLICATION_OCTETS
:
3079 case APPLICATION_POSTSCRIPT
:
3080 return "postscript";
3082 return "unknown_app_subtype";
3086 case MESSAGE_RFC822
:
3088 case MESSAGE_PARTIAL
:
3090 case MESSAGE_EXTERNAL
:
3093 return "unknown_msg_subtype";
3099 case MULTI_ALTERNATE
:
3100 return "alternative";
3103 case MULTI_PARALLEL
:
3108 return "unknown_multipart_subtype";
3119 return "unknown_text_subtype";
3122 return "unknown_type";
3128 ct_str_type (const char *type
) {
3129 struct str2init
*s2i
;
3131 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3132 if (! strcasecmp (type
, s2i
->si_key
)) {
3136 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3145 ct_str_subtype (int type
, const char *subtype
) {
3149 case CT_APPLICATION
:
3150 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3151 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3155 return kv
->kv_value
;
3157 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3158 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3162 return kv
->kv_value
;
3164 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3165 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3169 return kv
->kv_value
;
3171 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3172 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3176 return kv
->kv_value
;
3183 /* Find the content type and InitFunc for the CT. */
3184 const struct str2init
*
3185 get_ct_init (int type
) {
3186 const struct str2init
*sp
;
3188 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3189 if (type
== sp
->si_val
) {
3198 ce_str (int encoding
) {
3203 return "quoted-printable";
3219 /* Find the content type and InitFunc for the content encoding method. */
3220 const struct str2init
*
3221 get_ce_method (const char *method
) {
3222 struct str2init
*sp
;
3224 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3225 if (! strcasecmp (method
, sp
->si_key
)) {
3234 * Parse a series of MIME attributes (or parameters) given a header as
3237 * Arguments include:
3239 * filename - Name of input file (for error messages)
3240 * fieldname - Name of field being processed
3241 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3242 * Updated to point to end of attributes when finished.
3243 * param_head - Pointer to head of parameter list
3244 * param_tail - Pointer to tail of parameter list
3245 * commentp - Pointer to header comment pointer (may be NULL)
3247 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3248 * DONE to indicate a benign error (minor parsing error, but the program
3253 parse_header_attrs (const char *filename
, const char *fieldname
,
3254 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3257 char *cp
= *header_attrp
;
3263 struct sectlist
*next
;
3269 struct sectlist
*sechead
;
3270 struct parmlist
*next
;
3271 } *pp
, *pp2
, *phead
= NULL
;
3273 while (*cp
== ';') {
3274 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3275 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3278 while (isspace ((unsigned char) *cp
))
3282 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3287 if (! suppress_extraneous_trailing_semicolon_warning
) {
3288 inform("extraneous trailing ';' in message %s's %s: "
3289 "parameter list", filename
, fieldname
);
3294 /* down case the attribute name */
3295 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3296 *dp
= tolower ((unsigned char) *dp
);
3298 for (up
= dp
; isspace ((unsigned char) *dp
);)
3300 if (dp
== cp
|| *dp
!= '=') {
3301 inform("invalid parameter in message %s's %s: "
3302 "field\n%*sparameter %s (error detected at offset %d)",
3303 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3308 * To handle RFC 2231, we have to deal with the following extensions:
3310 * name*=encoded-value
3311 * name*<N>=part-N-of-a-parameter-value
3312 * name*<N>*=encoded-part-N-of-a-parameter-value
3315 * If there's a * right before the equal sign, it's encoded.
3316 * If there's a * and one or more digits, then it's section N.
3318 * Remember we can have one or the other, or both. cp points to
3319 * beginning of name, up points past the last character in the
3323 for (vp
= cp
; vp
< up
; vp
++) {
3324 if (*vp
== '*' && vp
< up
- 1) {
3328 if (*vp
== '*' && vp
== up
- 1) {
3330 } else if (partial
) {
3331 if (isdigit((unsigned char) *vp
))
3332 index
= *vp
- '0' + index
* 10;
3334 inform("invalid parameter index in message %s's "
3335 "%s: field\n%*s(parameter %s)", filename
,
3336 fieldname
, strlen(invo_name
) + 2, "", cp
);
3345 * Break out the parameter name and value sections and allocate
3349 nameptr
= mh_xmalloc(len
+ 1);
3350 strncpy(nameptr
, cp
, len
);
3351 nameptr
[len
] = '\0';
3353 for (dp
++; isspace ((unsigned char) *dp
);)
3358 * Single quotes delimit the character set and language tag.
3359 * They are required on the first section (or a complete
3364 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3370 charset
= mh_xmalloc(len
+ 1);
3371 strncpy(charset
, dp
, len
);
3372 charset
[len
] = '\0';
3378 inform("missing charset in message %s's %s: "
3379 "field\n%*s(parameter %s)", filename
, fieldname
,
3380 strlen(invo_name
) + 2, "", nameptr
);
3386 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3393 lang
= mh_xmalloc(len
+ 1);
3394 strncpy(lang
, dp
, len
);
3401 inform("missing language tag in message %s's %s: "
3402 "field\n%*s(parameter %s)", filename
, fieldname
,
3403 strlen(invo_name
) + 2, "", nameptr
);
3413 * At this point vp should be pointing at the beginning
3414 * of the encoded value/section. Continue until we reach
3415 * the end or get whitespace. But first, calculate the
3416 * length so we can allocate the correct buffer size.
3419 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3421 if (*(vp
+ 1) == '\0' ||
3422 !isxdigit((unsigned char) *(vp
+ 1)) ||
3423 *(vp
+ 2) == '\0' ||
3424 !isxdigit((unsigned char) *(vp
+ 2))) {
3425 inform("invalid encoded sequence in message "
3426 "%s's %s: field\n%*s(parameter %s)",
3427 filename
, fieldname
, strlen(invo_name
) + 2,
3439 up
= valptr
= mh_xmalloc(len
+ 1);
3441 for (vp
= dp
; istoken(*vp
); vp
++) {
3443 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3454 * A "normal" string. If it's got a leading quote, then we
3455 * strip the quotes out. Otherwise go until we reach the end
3456 * or get whitespace. Note we scan it twice; once to get the
3457 * length, then the second time copies it into the destination
3464 for (cp
= dp
+ 1;;) {
3468 inform("invalid quoted-string in message %s's %s: "
3469 "field\n%*s(parameter %s)", filename
,
3470 fieldname
, strlen(invo_name
) + 2, "", nameptr
);
3490 for (cp
= dp
; istoken (*cp
); cp
++) {
3495 valptr
= mh_xmalloc(len
+ 1);
3499 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3507 strncpy(valptr
, cp
= dp
, len
);
3515 * If 'partial' is set, we don't allocate a parameter now. We
3516 * put it on the parameter linked list to be reassembled later.
3518 * "phead" points to a list of all parameters we need to reassemble.
3519 * Each parameter has a list of sections. We insert the sections in
3524 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3525 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3540 * Insert this into the section linked list
3548 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3549 sp
->next
= pp
->sechead
;
3552 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3553 if (sp2
->index
== sp
->index
) {
3554 inform("duplicate index (%d) in message "
3555 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3556 filename
, fieldname
, strlen(invo_name
) + 2, "",
3560 if (sp2
->index
< sp
->index
&&
3561 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3562 sp
->next
= sp2
->next
;
3569 inform("Internal error: cannot insert partial "
3570 "param in message %s's %s: field\n%*s(parameter %s)",
3571 filename
, fieldname
, strlen(invo_name
) + 2, "",
3578 * Save our charset and lang tags.
3581 if (index
== 0 && encoded
) {
3582 mh_xfree(pp
->charset
);
3583 pp
->charset
= charset
;
3588 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3589 pm
->pm_charset
= charset
;
3593 while (isspace ((unsigned char) *cp
))
3597 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3603 * Now that we're done, reassemble all of the partial parameters.
3606 for (pp
= phead
; pp
!= NULL
; ) {
3610 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3611 if (sp
->index
!= pindex
++) {
3612 inform("missing section %d for parameter in "
3613 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3614 filename
, fieldname
, strlen(invo_name
) + 2, "",
3621 p
= q
= mh_xmalloc(tlen
+ 1);
3622 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3623 memcpy(q
, sp
->value
, sp
->len
);
3633 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3634 pm
->pm_charset
= pp
->charset
;
3635 pm
->pm_lang
= pp
->lang
;
3646 * Return the charset for a particular content type.
3650 content_charset (CT ct
) {
3651 char *ret_charset
= NULL
;
3653 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3655 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3660 * Create a string based on a list of output parameters. Assume that this
3661 * parameter string will be appended to an existing header, so start out
3662 * with the separator (;). Perform RFC 2231 encoding when necessary.
3666 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3668 char *paramout
= NULL
;
3669 char line
[CPERLIN
* 2], *q
;
3670 int curlen
, index
, cont
, encode
, i
;
3671 size_t valoff
, numchars
;
3673 while (params
!= NULL
) {
3679 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3682 if (strlen(params
->pm_name
) > CPERLIN
) {
3683 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3688 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3691 * Loop until we get a parameter that fits within a line. We
3692 * assume new lines start with a tab, so check our overflow based
3702 * At this point we're definitely continuing the line, so
3703 * be sure to include the parameter name and section index.
3706 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3707 params
->pm_name
, index
);
3710 * Both of these functions do a NUL termination
3714 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3715 numchars
, valoff
, index
);
3717 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3727 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3732 * "line" starts with a ;\n\t, so that doesn't count against
3733 * the length. But add 8 since it starts with a tab; that's
3734 * how we end up with 5.
3737 initialwidth
= strlen(line
) + 5;
3740 * At this point the line should be built, so add it to our
3741 * current output buffer.
3744 paramout
= add(line
, paramout
);
3748 * If this won't fit on the line, start a new one. Save room in
3749 * case we need a semicolon on the end
3752 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3764 * At this point, we're either finishing a continued parameter, or
3765 * we're working on a new one.
3769 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3770 params
->pm_name
, index
);
3772 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3777 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3778 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3780 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3781 strlen(params
->pm_value
+ valoff
), valoff
);
3788 paramout
= add(line
, paramout
);
3789 initialwidth
+= strlen(line
);
3791 params
= params
->pm_next
;
3795 *offsetout
= initialwidth
;
3801 * Calculate the size of a parameter.
3805 * pm - The parameter being output
3806 * index - If continuing the parameter, the index of the section
3808 * valueoff - The current offset into the parameter value that we're
3809 * working on (previous sections have consumed valueoff bytes).
3810 * encode - Set if we should perform encoding on this parameter section
3811 * (given that we're consuming bytesfit bytes).
3812 * cont - Set if the remaining data in value will not fit on a single
3813 * line and will need to be continued.
3814 * bytesfit - The number of bytes that we can consume from the parameter
3815 * value and still fit on a completely new line. The
3816 * calculation assumes the new line starts with a tab,
3817 * includes the parameter name and any encoding, and fits
3818 * within CPERLIN bytes. Will always be at least 1.
3822 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3825 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3826 size_t len
= 0, fit
= 0;
3827 int fitlimit
= 0, eightbit
, maxfit
;
3832 * Add up the length. First, start with the parameter name.
3835 len
= strlen(pm
->pm_name
);
3838 * Scan the parameter value and see if we need to do encoding for this
3842 eightbit
= contains8bit(start
, NULL
);
3845 * Determine if we need to encode this section. Encoding is necessary if:
3847 * - There are any 8-bit characters at all and we're on the first
3849 * - There are 8-bit characters within N bytes of our section start.
3850 * N is calculated based on the number of bytes it would take to
3851 * reach CPERLIN. Specifically:
3852 * 8 (starting tab) +
3853 * strlen(param name) +
3854 * 4 ('* for section marker, '=', opening/closing '"')
3856 * is the number of bytes used by everything that isn't part of the
3857 * value. So that gets subtracted from CPERLIN.
3860 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3861 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3862 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3866 len
++; /* Add in equal sign */
3870 * We're using maxfit as a marker for how many characters we can
3871 * fit into the line. Bump it by two because we're not using quotes
3878 * If we don't have a charset or language tag in this parameter,
3882 if (! pm
->pm_charset
) {
3883 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3884 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3885 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3886 "local character set is US-ASCII", pm
->pm_name
);
3889 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3891 len
++; /* For the encoding marker */
3894 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3899 * We know we definitely need to include an index. maxfit already
3900 * includes the section marker.
3902 len
+= strlen(indexchar
);
3904 for (p
= start
; *p
!= '\0'; p
++) {
3905 if (isparamencode(*p
)) {
3913 * Just so there's no confusion: maxfit is counting OUTPUT
3914 * characters (post-encoding). fit is counting INPUT characters.
3916 if (! fitlimit
&& maxfit
>= 0)
3918 else if (! fitlimit
)
3923 * Calculate the string length, but add room for quoting \
3924 * and " if necessary. Also account for quotes at beginning
3927 for (p
= start
; *p
!= '\0'; p
++) {
3938 if (! fitlimit
&& maxfit
>= 0)
3940 else if (! fitlimit
)
3957 * Output an encoded parameter string.
3961 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3962 size_t valueoff
, int index
)
3964 size_t outlen
= 0, n
;
3965 char *endptr
= output
+ len
, *p
;
3968 * First, output the marker for an encoded string.
3976 * If the index is 0, output the character set and language tag.
3977 * If theses were NULL, they should have already been filled in
3982 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3986 if (output
> endptr
) {
3987 inform("Internal error: parameter buffer overflow");
3993 * Copy over the value, encoding if necessary
3996 p
= pm
->pm_value
+ valueoff
;
3997 while (valuelen
-- > 0) {
3998 if (isparamencode(*p
)) {
3999 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4006 if (output
> endptr
) {
4007 inform("Internal error: parameter buffer overflow");
4018 * Output a "normal" parameter, without encoding. Be sure to escape
4019 * quotes and backslashes if necessary.
4023 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4027 char *endptr
= output
+ len
, *p
;
4033 p
= pm
->pm_value
+ valueoff
;
4035 while (valuelen
-- > 0) {
4046 if (output
> endptr
) {
4047 inform("Internal error: parameter buffer overflow");
4052 if (output
- 2 > endptr
) {
4053 inform("Internal error: parameter buffer overflow");
4064 * Add a parameter to the parameter linked list
4068 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4073 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4074 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4077 (*last
)->pm_next
= pm
;
4088 * Either replace a current parameter with a new value, or add the parameter
4089 * to the parameter linked list.
4093 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4097 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4098 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4100 * If nocopy is set, it's assumed that we own both name
4101 * and value. We don't need name, so we discard it now.
4106 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4111 return add_param(first
, last
, name
, value
, nocopy
);
4115 * Retrieve a parameter value from a parameter linked list. If the parameter
4116 * value needs converted to the local character set, do that now.
4120 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4122 while (first
!= NULL
) {
4123 if (strcasecmp(name
, first
->pm_name
) == 0) {
4125 return first
->pm_value
;
4126 return getcpy(get_param_value(first
, replace
));
4128 first
= first
->pm_next
;
4135 * Return a parameter value, converting to the local character set if
4139 char *get_param_value(PM pm
, char replace
)
4141 static char buffer
[4096]; /* I hope no parameters are larger */
4142 size_t bufsize
= sizeof(buffer
);
4147 ICONV_CONST
char *p
;
4148 #else /* HAVE_ICONV */
4150 #endif /* HAVE_ICONV */
4155 * If we don't have a character set indicated, it's assumed to be
4156 * US-ASCII. If it matches our character set, we don't need to convert
4160 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4161 strlen(pm
->pm_charset
))) {
4162 return pm
->pm_value
;
4166 * In this case, we need to convert. If we have iconv support, use
4167 * that. Otherwise, go through and simply replace every non-ASCII
4168 * character with the substitution character.
4173 bufsize
= sizeof(buffer
);
4174 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4176 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4177 if (cd
== (iconv_t
) -1) {
4181 inbytes
= strlen(pm
->pm_value
);
4185 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4186 if (errno
!= EILSEQ
) {
4191 * Reset shift state, substitute our character,
4192 * try to restart conversion.
4195 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4208 for (++p
, --inbytes
;
4209 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4228 #endif /* HAVE_ICONV */
4231 * Take everything non-ASCII and substitute the replacement character
4235 bufsize
= sizeof(buffer
);
4236 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4237 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))