2 * mhparse.c -- routines to parse the contents of MIME messages
4 * This code is Copyright (c) 2002, by the authors of nmh. See the
5 * COPYRIGHT file in the root directory of the nmh distribution for
6 * complete copyright information.
15 #include <h/mhparse.h>
19 #endif /* HAVE_ICONV */
25 extern int rcachesw
; /* mhcachesbr.c */
26 extern int wcachesw
; /* mhcachesbr.c */
28 int checksw
= 0; /* check Content-MD5 field */
31 * These are for mhfixmsg to:
32 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
34 * 2) Suppress the warning about bogus multipart content, and report it.
35 * 3) Suppress the warning about extraneous trailing ';' in header parameter
38 int skip_mp_cte_check
;
39 int suppress_bogus_mp_content_warning
;
41 int suppress_extraneous_trailing_semicolon_warning
;
44 * By default, suppress warning about multiple MIME-Version header fields.
46 int suppress_multiple_mime_version_warning
= 1;
48 /* list of preferred type/subtype pairs, for -prefer */
49 char *preferred_types
[NPREFS
],
50 *preferred_subtypes
[NPREFS
];
55 * Structures for TEXT messages
57 struct k2v SubText
[] = {
58 { "plain", TEXT_PLAIN
},
59 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC-1341 */
60 { "enriched", TEXT_ENRICHED
}, /* defined in RFC-1896 */
61 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
64 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
67 * Structures for MULTIPART messages
69 struct k2v SubMultiPart
[] = {
70 { "mixed", MULTI_MIXED
},
71 { "alternative", MULTI_ALTERNATE
},
72 { "digest", MULTI_DIGEST
},
73 { "parallel", MULTI_PARALLEL
},
74 { "related", MULTI_RELATED
},
75 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
79 * Structures for MESSAGE messages
81 struct k2v SubMessage
[] = {
82 { "rfc822", MESSAGE_RFC822
},
83 { "partial", MESSAGE_PARTIAL
},
84 { "external-body", MESSAGE_EXTERNAL
},
85 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
89 * Structure for APPLICATION messages
91 struct k2v SubApplication
[] = {
92 { "octet-stream", APPLICATION_OCTETS
},
93 { "postscript", APPLICATION_POSTSCRIPT
},
94 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
98 * Mapping of names of CTE types in mhbuild directives
100 static struct k2v EncodingType
[] = {
103 { "q-p", CE_QUOTED
},
104 { "quoted-printable", CE_QUOTED
},
105 { "b64", CE_BASE64
},
106 { "base64", CE_BASE64
},
112 int find_cache (CT
, int, int *, char *, char *, int);
116 int type_ok (CT
, int);
117 void content_error (char *, CT
, char *, ...);
120 void free_encoding (CT
, int);
125 static CT
get_content (FILE *, char *, int);
126 static int get_comment (const char *, const char *, char **, char **);
128 static int InitGeneric (CT
);
129 static int InitText (CT
);
130 static int InitMultiPart (CT
);
131 static void reverse_parts (CT
);
132 static void prefer_parts(CT ct
);
133 static int InitMessage (CT
);
134 static int InitApplication (CT
);
135 static int init_encoding (CT
, OpenCEFunc
);
136 static unsigned long size_encoding (CT
);
137 static int InitBase64 (CT
);
138 static int openBase64 (CT
, char **);
139 static int InitQuoted (CT
);
140 static int openQuoted (CT
, char **);
141 static int Init7Bit (CT
);
142 static int openExternal (CT
, CT
, CE
, char **, int *);
143 static int InitFile (CT
);
144 static int openFile (CT
, char **);
145 static int InitFTP (CT
);
146 static int openFTP (CT
, char **);
147 static int InitMail (CT
);
148 static int openMail (CT
, char **);
149 static int readDigest (CT
, char *);
150 static int get_leftover_mp_content (CT
, int);
151 static int InitURL (CT
);
152 static int openURL (CT
, char **);
153 static int parse_header_attrs (const char *, const char *, char **, PM
*,
155 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
156 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
157 static int get_dispo (char *, CT
, int);
159 struct str2init str2cts
[] = {
160 { "application", CT_APPLICATION
, InitApplication
},
161 { "audio", CT_AUDIO
, InitGeneric
},
162 { "image", CT_IMAGE
, InitGeneric
},
163 { "message", CT_MESSAGE
, InitMessage
},
164 { "multipart", CT_MULTIPART
, InitMultiPart
},
165 { "text", CT_TEXT
, InitText
},
166 { "video", CT_VIDEO
, InitGeneric
},
167 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
168 { NULL
, CT_UNKNOWN
, NULL
},
171 struct str2init str2ces
[] = {
172 { "base64", CE_BASE64
, InitBase64
},
173 { "quoted-printable", CE_QUOTED
, InitQuoted
},
174 { "8bit", CE_8BIT
, Init7Bit
},
175 { "7bit", CE_7BIT
, Init7Bit
},
176 { "binary", CE_BINARY
, Init7Bit
},
177 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
178 { NULL
, CE_UNKNOWN
, NULL
},
182 * NOTE WELL: si_key MUST NOT have value of NOTOK
184 * si_key is 1 if access method is anonymous.
186 struct str2init str2methods
[] = {
187 { "afs", 1, InitFile
},
188 { "anon-ftp", 1, InitFTP
},
189 { "ftp", 0, InitFTP
},
190 { "local-file", 0, InitFile
},
191 { "mail-server", 0, InitMail
},
192 { "url", 0, InitURL
},
198 * Main entry point for parsing a MIME message or file.
199 * It returns the Content structure for the top level
200 * entity in the file.
204 parse_mime (char *file
)
213 bogus_mp_content
= 0;
216 * Check if file is actually standard input
218 if ((is_stdin
= !(strcmp (file
, "-")))) {
219 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
221 advise("mhparse", "unable to create temporary file in %s",
225 file
= mh_xstrdup(tfile
);
227 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
228 if (fwrite(buffer
, 1, n
, fp
) != n
) {
229 (void) m_unlink (file
);
230 advise (file
, "error copying to temporary file");
236 if (ferror (stdin
)) {
237 (void) m_unlink (file
);
238 advise ("stdin", "error reading");
242 (void) m_unlink (file
);
243 advise (file
, "error writing");
246 fseek (fp
, 0L, SEEK_SET
);
247 } else if (stat (file
, &statbuf
) == NOTOK
) {
248 advise (file
, "unable to stat");
250 } else if (S_ISDIR(statbuf
.st_mode
)) {
251 /* Don't try to parse a directory. */
252 inform("%s is a directory", file
);
254 } else if ((fp
= fopen (file
, "r")) == NULL
) {
255 advise (file
, "unable to read");
259 if (!(ct
= get_content (fp
, file
, 1))) {
261 (void) m_unlink (file
);
262 inform("unable to decode %s", file
);
267 ct
->c_unlink
= 1; /* temp file to remove */
271 if (ct
->c_end
== 0L) {
272 fseek (fp
, 0L, SEEK_END
);
273 ct
->c_end
= ftell (fp
);
276 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
288 * Main routine for reading/parsing the headers
289 * of a message content.
291 * toplevel = 1 # we are at the top level of the message
292 * toplevel = 0 # we are inside message type or multipart type
293 * # other than multipart/digest
294 * toplevel = -1 # we are inside multipart/digest
295 * NB: on failure we will fclose(in)!
299 get_content (FILE *in
, char *file
, int toplevel
)
302 char buf
[BUFSIZ
], name
[NAMESZ
];
306 m_getfld_state_t gstate
= 0;
308 /* allocate the content structure */
311 ct
->c_file
= add (file
, NULL
);
312 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
315 * Parse the header fields for this
316 * content into a linked list.
318 m_getfld_track_filepos (&gstate
, in
);
319 for (compnum
= 1;;) {
320 int bufsz
= sizeof buf
;
321 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
326 /* get copies of the buffers */
327 np
= mh_xstrdup(name
);
328 vp
= mh_xstrdup(buf
);
330 /* if necessary, get rest of field */
331 while (state
== FLDPLUS
) {
333 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
334 vp
= add (buf
, vp
); /* add to previous value */
337 /* Now add the header data to the list */
338 add_header (ct
, np
, vp
);
340 /* continue, to see if this isn't the last header field */
341 ct
->c_begin
= ftell (in
) + 1;
345 if (name
[0] == ':') {
346 /* Special case: no blank line between header and body. The
347 file position indicator is on the newline at the end of the
348 line, but it needs to be one prior to the beginning of the
349 line. So subtract the length of the line, bufsz, plus 1. */
350 ct
->c_begin
= ftell (in
) - (bufsz
+ 1);
352 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
357 ct
->c_begin
= ftell (in
);
362 adios (NULL
, "message format error in component #%d", compnum
);
365 adios (NULL
, "getfld() returned %d", state
);
368 /* break out of the loop */
371 m_getfld_state_destroy (&gstate
);
374 * Read the content headers. We will parse the
375 * MIME related header fields into their various
376 * structures and set internal flags related to
377 * content type/subtype, etc.
380 hp
= ct
->c_first_hf
; /* start at first header field */
382 /* Get MIME-Version field */
383 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
388 vrsn
= add (hp
->value
, NULL
);
390 /* Now, cleanup this field */
393 while (isspace ((unsigned char) *cp
))
395 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
397 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
398 if (!isspace ((unsigned char) *dp
))
402 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
405 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
408 for (dp
= cp
; istoken (*dp
); dp
++)
412 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
415 inform("message %s has unknown value for %s: field (%s), continuing...",
416 ct
->c_file
, VRSN_FIELD
, cp
);
421 if (! suppress_multiple_mime_version_warning
)
422 inform("message %s has multiple %s: fields",
423 ct
->c_file
, VRSN_FIELD
);
427 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
428 /* Get Content-Type field */
429 struct str2init
*s2i
;
430 CI ci
= &ct
->c_ctinfo
;
432 /* Check if we've already seen a Content-Type header */
434 inform("message %s has multiple %s: fields",
435 ct
->c_file
, TYPE_FIELD
);
439 /* Parse the Content-Type field */
440 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
444 * Set the Init function and the internal
445 * flag for this content type.
447 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
448 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
450 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
452 ct
->c_type
= s2i
->si_val
;
453 ct
->c_ctinitfnx
= s2i
->si_init
;
455 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
456 /* Get Content-Transfer-Encoding field */
458 struct str2init
*s2i
;
461 * Check if we've already seen the
462 * Content-Transfer-Encoding field
465 inform("message %s has multiple %s: fields",
466 ct
->c_file
, ENCODING_FIELD
);
470 /* get copy of this field */
471 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
473 while (isspace ((unsigned char) *cp
))
475 for (dp
= cp
; istoken (*dp
); dp
++)
481 * Find the internal flag and Init function
482 * for this transfer encoding.
484 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
485 if (!strcasecmp (cp
, s2i
->si_key
))
487 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
490 ct
->c_encoding
= s2i
->si_val
;
492 /* Call the Init function for this encoding */
493 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
496 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
497 /* Get Content-MD5 field */
503 if (ct
->c_digested
) {
504 inform("message %s has multiple %s: fields",
505 ct
->c_file
, MD5_FIELD
);
509 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
511 while (isspace ((unsigned char) *cp
))
513 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
515 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
516 if (!isspace ((unsigned char) *dp
))
520 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
523 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
528 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
536 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
537 /* Get Content-ID field */
538 ct
->c_id
= add (hp
->value
, ct
->c_id
);
540 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
541 /* Get Content-Description field */
542 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
544 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
545 /* Get Content-Disposition field */
546 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
551 hp
= hp
->next
; /* next header field */
555 * Check if we saw a Content-Type field.
556 * If not, then assign a default value for
557 * it, and the Init function.
561 * If we are inside a multipart/digest message,
562 * so default type is message/rfc822
565 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
567 ct
->c_type
= CT_MESSAGE
;
568 ct
->c_ctinitfnx
= InitMessage
;
571 * Else default type is text/plain
573 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
575 ct
->c_type
= CT_TEXT
;
576 ct
->c_ctinitfnx
= InitText
;
580 /* Use default Transfer-Encoding, if necessary */
582 ct
->c_encoding
= CE_7BIT
;
595 * small routine to add header field to list
599 add_header (CT ct
, char *name
, char *value
)
603 /* allocate header field structure */
606 /* link data into header structure */
611 /* link header structure into the list */
612 if (ct
->c_first_hf
== NULL
) {
613 ct
->c_first_hf
= hp
; /* this is the first */
616 ct
->c_last_hf
->next
= hp
; /* add it to the end */
625 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
626 * directives. Fills in the information of the CTinfo structure.
629 get_ctinfo (char *cp
, CT ct
, int magic
)
638 /* store copy of Content-Type line */
639 cp
= ct
->c_ctline
= add (cp
, NULL
);
641 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
644 /* change newlines to spaces */
645 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
648 /* trim trailing spaces */
649 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
650 if (!isspace ((unsigned char) *dp
))
655 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
657 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
658 &ci
->ci_comment
) == NOTOK
)
661 for (dp
= cp
; istoken (*dp
); dp
++)
664 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
668 inform("invalid %s: field in message %s (empty type)",
669 TYPE_FIELD
, ct
->c_file
);
672 to_lower(ci
->ci_type
);
674 while (isspace ((unsigned char) *cp
))
677 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
678 &ci
->ci_comment
) == NOTOK
)
683 ci
->ci_subtype
= mh_xstrdup("");
688 while (isspace ((unsigned char) *cp
))
691 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
692 &ci
->ci_comment
) == NOTOK
)
695 for (dp
= cp
; istoken (*dp
); dp
++)
698 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
701 if (!*ci
->ci_subtype
) {
702 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
703 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
706 to_lower(ci
->ci_subtype
);
709 while (isspace ((unsigned char) *cp
))
712 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
713 &ci
->ci_comment
) == NOTOK
)
716 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
717 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
718 &ci
->ci_comment
)) != OK
) {
719 return status
== NOTOK
? NOTOK
: OK
;
723 * Get any <Content-Id> given in buffer
725 if (magic
&& *cp
== '<') {
728 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
729 inform("invalid ID in message %s", ct
->c_file
);
735 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
741 while (isspace ((unsigned char) *cp
))
746 * Get any [Content-Description] given in buffer.
748 if (magic
&& *cp
== '[') {
750 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
754 inform("invalid description in message %s", ct
->c_file
);
762 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
768 while (isspace ((unsigned char) *cp
))
773 * Get any {Content-Disposition} given in buffer.
775 if (magic
&& *cp
== '{') {
777 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
781 inform("invalid disposition in message %s", ct
->c_file
);
789 if (get_dispo(cp
, ct
, 1) != OK
)
795 while (isspace ((unsigned char) *cp
))
800 * Get any extension directives (right now just the content transfer
801 * encoding, but maybe others) that we care about.
804 if (magic
&& *cp
== '*') {
806 * See if it's a CTE we match on
811 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
815 inform("invalid null transfer encoding specification");
822 ct
->c_reqencoding
= CE_UNKNOWN
;
824 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
825 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
826 ct
->c_reqencoding
= kv
->kv_value
;
831 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
832 inform("invalid CTE specification: \"%s\"", dp
);
836 while (isspace ((unsigned char) *cp
))
841 * Check if anything is left over
845 ci
->ci_magic
= mh_xstrdup(cp
);
847 /* If there is a Content-Disposition header and it doesn't
848 have a *filename=, extract it from the magic contents.
849 The r1bindex call skips any leading directory
851 if (ct
->c_dispo_type
&&
852 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
853 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
854 r1bindex(ci
->ci_magic
, '/'), 0);
858 inform("extraneous information in message %s's %s: field\n%*s(%s)",
859 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
867 * Parse out a Content-Disposition header. A lot of this is cribbed from
871 get_dispo (char *cp
, CT ct
, int buildflag
)
873 char *dp
, *dispoheader
;
878 * Save the whole copy of the Content-Disposition header, unless we're
879 * processing a mhbuild directive. A NULL c_dispo will be a flag to
880 * mhbuild that the disposition header needs to be generated at that
884 dispoheader
= cp
= add(cp
, NULL
);
886 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
889 /* change newlines to spaces */
890 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
893 /* trim trailing spaces */
894 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
895 if (!isspace ((unsigned char) *dp
))
900 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
902 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
908 for (dp
= cp
; istoken (*dp
); dp
++)
911 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
914 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
917 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
918 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
920 if (status
== NOTOK
) {
925 inform("extraneous information in message %s's %s: field\n%*s(%s)",
926 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
932 ct
->c_dispo
= dispoheader
;
939 get_comment (const char *filename
, const char *fieldname
, char **ap
,
944 char c
, buffer
[BUFSIZ
], *dp
;
954 inform("invalid comment in message %s's %s: field",
955 filename
, fieldname
);
960 if ((c
= *cp
++) == '\0')
983 if ((dp
= *commentp
)) {
984 *commentp
= concat (dp
, " ", buffer
, NULL
);
987 *commentp
= mh_xstrdup(buffer
);
991 while (isspace ((unsigned char) *cp
))
1002 * Handles content types audio, image, and video.
1003 * There's not much to do right here.
1011 return OK
; /* not much to do here */
1022 char buffer
[BUFSIZ
];
1027 CI ci
= &ct
->c_ctinfo
;
1029 /* check for missing subtype */
1030 if (!*ci
->ci_subtype
)
1031 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1034 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1036 /* allocate text character set structure */
1038 ct
->c_ctparams
= (void *) t
;
1040 /* scan for charset parameter */
1041 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1042 if (!strcasecmp (pm
->pm_name
, "charset"))
1045 /* check if content specified a character set */
1047 chset
= pm
->pm_value
;
1048 t
->tx_charset
= CHARSET_SPECIFIED
;
1050 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1054 * If we can not handle character set natively,
1055 * then check profile for string to modify the
1056 * terminal or display method.
1058 * termproc is for mhshow, though mhlist -debug prints it, too.
1060 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1061 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1062 if ((cp
= context_find (buffer
)))
1063 ct
->c_termproc
= mh_xstrdup(cp
);
1075 InitMultiPart (CT ct
)
1085 struct multipart
*m
;
1086 struct part
*part
, **next
;
1087 CI ci
= &ct
->c_ctinfo
;
1092 * The encoding for multipart messages must be either
1093 * 7bit, 8bit, or binary (per RFC2045).
1095 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1096 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1097 /* Copy the Content-Transfer-Encoding header field body so we can
1098 remove any trailing whitespace and leading blanks from it. */
1099 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1101 bp
= cte
+ strlen (cte
) - 1;
1102 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1103 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1105 inform("\"%s/%s\" type in message %s must be encoded in\n"
1106 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1107 "mhfixmsg -fixcte can fix it, or\n"
1108 "manually edit the file and change the \"%s\"\n"
1109 "Content-Transfer-Encoding to one of those. For now, continuing...",
1110 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1117 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1120 * Check for "boundary" parameter, which is
1121 * required for multipart messages.
1124 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1125 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1131 /* complain if boundary parameter is missing */
1133 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1134 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1138 /* allocate primary structure for multipart info */
1140 ct
->c_ctparams
= (void *) m
;
1142 /* check if boundary parameter contains only whitespace characters */
1143 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1146 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1147 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1151 /* remove trailing whitespace from boundary parameter */
1152 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1153 if (!isspace ((unsigned char) *dp
))
1157 /* record boundary separators */
1158 m
->mp_start
= concat (bp
, "\n", NULL
);
1159 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1161 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1162 advise (ct
->c_file
, "unable to open for reading");
1166 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1168 next
= &m
->mp_parts
;
1172 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1177 if (bufp
[0] != '-' || bufp
[1] != '-')
1180 if (strcmp (bufp
+ 2, m
->mp_start
))
1185 next
= &part
->mp_next
;
1187 if (!(p
= get_content (fp
, ct
->c_file
,
1188 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1196 fseek (fp
, pos
, SEEK_SET
);
1199 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1203 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1204 if (p
->c_end
< p
->c_begin
)
1205 p
->c_begin
= p
->c_end
;
1210 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1215 if (! suppress_bogus_mp_content_warning
) {
1216 inform("bogus multipart content in message %s", ct
->c_file
);
1218 bogus_mp_content
= 1;
1220 if (!inout
&& part
) {
1222 p
->c_end
= ct
->c_end
;
1224 if (p
->c_begin
>= p
->c_end
) {
1225 for (next
= &m
->mp_parts
; *next
!= part
;
1226 next
= &((*next
)->mp_next
))
1235 /* reverse the order of the parts for multipart/alternative */
1236 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1242 * label all subparts with part number, and
1243 * then initialize the content of the subpart.
1248 char partnam
[BUFSIZ
];
1251 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1252 pp
= partnam
+ strlen (partnam
);
1257 for (part
= m
->mp_parts
, partnum
= 1; part
;
1258 part
= part
->mp_next
, partnum
++) {
1261 sprintf (pp
, "%d", partnum
);
1262 p
->c_partno
= mh_xstrdup(partnam
);
1264 /* initialize the content of the subparts */
1265 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1274 get_leftover_mp_content (ct
, 1);
1275 get_leftover_mp_content (ct
, 0);
1285 * reverse the order of the parts of a multipart/alternative,
1286 * presumably to put the "most favored" alternative first, for
1287 * ease of choosing/displaying it later on. from a mail message on
1288 * nmh-workers, from kenh:
1289 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1290 * see code in mhn that did the same thing... Acccording to the RCS
1291 * logs, that code was around from the initial checkin of mhn.c by
1292 * John Romine in 1992, which is as far back as we have."
1295 reverse_parts (CT ct
)
1297 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1301 /* Reverse the order of its parts by walking the mp_parts list
1302 and pushing each node to the front. */
1303 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1304 next
= part
->mp_next
;
1305 part
->mp_next
= m
->mp_parts
;
1311 move_preferred_part (CT ct
, char *type
, char *subtype
)
1313 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1314 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1318 /* move the matching part(s) to the head of the list: walk the
1319 * list of parts, move matching parts to a new list (maintaining
1320 * their order), and finally, concatenate the old list onto the
1327 head
->mp_next
= m
->mp_parts
;
1328 nhead
->mp_next
= NULL
;
1332 part
= head
->mp_next
;
1333 while (part
!= NULL
) {
1334 ci
= &part
->mp_part
->c_ctinfo
;
1335 if (!strcasecmp(ci
->ci_type
, type
) &&
1336 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1337 prev
->mp_next
= part
->mp_next
;
1338 part
->mp_next
= NULL
;
1339 ntail
->mp_next
= part
;
1341 part
= prev
->mp_next
;
1344 part
= prev
->mp_next
;
1347 ntail
->mp_next
= head
->mp_next
;
1348 m
->mp_parts
= nhead
->mp_next
;
1353 * move parts that match the user's preferences (-prefer) to the head
1354 * of the line. process preferences in reverse so first one given
1355 * ends up first in line
1361 for (i
= npreferred
-1; i
>= 0; i
--)
1362 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1367 /* parse_mime() arranges alternates in reverse (priority) order. This
1368 function can be used to reverse them back. This will put, for
1369 example, a text/plain part before a text/html part in a
1370 multipart/alternative part, for example, where it belongs. */
1372 reverse_alternative_parts (CT ct
) {
1373 if (ct
->c_type
== CT_MULTIPART
) {
1374 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1377 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1381 /* And call recursively on each part of a multipart. */
1382 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1383 reverse_alternative_parts (part
->mp_part
);
1396 CI ci
= &ct
->c_ctinfo
;
1398 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1399 inform("\"%s/%s\" type in message %s should be encoded in "
1400 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1405 /* check for missing subtype */
1406 if (!*ci
->ci_subtype
)
1407 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1410 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1412 switch (ct
->c_subtype
) {
1413 case MESSAGE_RFC822
:
1416 case MESSAGE_PARTIAL
:
1422 ct
->c_ctparams
= (void *) p
;
1424 /* scan for parameters "id", "number", and "total" */
1425 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1426 if (!strcasecmp (pm
->pm_name
, "id")) {
1427 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1430 if (!strcasecmp (pm
->pm_name
, "number")) {
1431 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1432 || p
->pm_partno
< 1) {
1434 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1435 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1436 ct
->c_file
, TYPE_FIELD
);
1441 if (!strcasecmp (pm
->pm_name
, "total")) {
1442 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1451 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1452 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1453 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1459 case MESSAGE_EXTERNAL
:
1467 ct
->c_ctparams
= (void *) e
;
1470 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1471 advise (ct
->c_file
, "unable to open for reading");
1475 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1477 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1485 p
->c_ceopenfnx
= NULL
;
1486 if ((exresult
= params_external (ct
, 0)) != NOTOK
1487 && p
->c_ceopenfnx
== openMail
) {
1491 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1493 content_error (NULL
, ct
,
1494 "empty body for access-type=mail-server");
1498 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1499 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1501 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1503 adios ("failed", "fread");
1506 adios (NULL
, "unexpected EOF from fread");
1509 bp
+= cc
, size
-= cc
;
1516 p
->c_end
= p
->c_begin
;
1521 if (exresult
== NOTOK
)
1523 if (e
->eb_flags
== NOTOK
)
1526 switch (p
->c_type
) {
1531 if (p
->c_subtype
!= MESSAGE_RFC822
)
1535 e
->eb_partno
= ct
->c_partno
;
1537 (*p
->c_ctinitfnx
) (p
);
1552 params_external (CT ct
, int composing
)
1555 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1556 CI ci
= &ct
->c_ctinfo
;
1558 ct
->c_ceopenfnx
= NULL
;
1559 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1560 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1561 struct str2init
*s2i
;
1562 CT p
= e
->eb_content
;
1564 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1565 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1568 e
->eb_access
= pm
->pm_value
;
1569 e
->eb_flags
= NOTOK
;
1570 p
->c_encoding
= CE_EXTERNAL
;
1573 e
->eb_access
= s2i
->si_key
;
1574 e
->eb_flags
= s2i
->si_val
;
1575 p
->c_encoding
= CE_EXTERNAL
;
1577 /* Call the Init function for this external type */
1578 if ((*s2i
->si_init
)(p
) == NOTOK
)
1582 if (!strcasecmp (pm
->pm_name
, "name")) {
1583 e
->eb_name
= pm
->pm_value
;
1586 if (!strcasecmp (pm
->pm_name
, "permission")) {
1587 e
->eb_permission
= pm
->pm_value
;
1590 if (!strcasecmp (pm
->pm_name
, "site")) {
1591 e
->eb_site
= pm
->pm_value
;
1594 if (!strcasecmp (pm
->pm_name
, "directory")) {
1595 e
->eb_dir
= pm
->pm_value
;
1598 if (!strcasecmp (pm
->pm_name
, "mode")) {
1599 e
->eb_mode
= pm
->pm_value
;
1602 if (!strcasecmp (pm
->pm_name
, "size")) {
1603 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1606 if (!strcasecmp (pm
->pm_name
, "server")) {
1607 e
->eb_server
= pm
->pm_value
;
1610 if (!strcasecmp (pm
->pm_name
, "subject")) {
1611 e
->eb_subject
= pm
->pm_value
;
1614 if (!strcasecmp (pm
->pm_name
, "url")) {
1616 * According to RFC 2017, we have to remove all whitespace from
1620 char *u
, *p
= pm
->pm_value
;
1621 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1623 for (; *p
!= '\0'; p
++) {
1624 if (! isspace((unsigned char) *p
))
1631 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1632 e
->eb_body
= getcpy (pm
->pm_value
);
1637 if (!e
->eb_access
) {
1638 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1639 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1652 InitApplication (CT ct
)
1654 CI ci
= &ct
->c_ctinfo
;
1657 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1664 * TRANSFER ENCODINGS
1668 init_encoding (CT ct
, OpenCEFunc openfnx
)
1670 ct
->c_ceopenfnx
= openfnx
;
1671 ct
->c_ceclosefnx
= close_encoding
;
1672 ct
->c_cesizefnx
= size_encoding
;
1679 close_encoding (CT ct
)
1681 CE ce
= &ct
->c_cefile
;
1690 static unsigned long
1691 size_encoding (CT ct
)
1696 CE ce
= &ct
->c_cefile
;
1699 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1700 return (long) st
.st_size
;
1703 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1704 return (long) st
.st_size
;
1708 if (ct
->c_encoding
== CE_EXTERNAL
)
1709 return (ct
->c_end
- ct
->c_begin
);
1712 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1713 return (ct
->c_end
- ct
->c_begin
);
1715 if (fstat (fd
, &st
) != NOTOK
)
1716 size
= (long) st
.st_size
;
1720 (*ct
->c_ceclosefnx
) (ct
);
1732 return init_encoding (ct
, openBase64
);
1737 openBase64 (CT ct
, char **file
)
1740 int fd
, own_ct_fp
= 0;
1741 char *cp
, *buffer
= NULL
;
1742 /* sbeck -- handle suffixes */
1744 CE ce
= &ct
->c_cefile
;
1745 unsigned char *decoded
;
1747 unsigned char digest
[16];
1750 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1755 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1756 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1762 if (*file
== NULL
) {
1765 ce
->ce_file
= mh_xstrdup(*file
);
1769 /* sbeck@cise.ufl.edu -- handle suffixes */
1771 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1772 if (ce
->ce_unlink
) {
1773 /* Create temporary file with filename extension. */
1774 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1775 adios(NULL
, "unable to create temporary file in %s",
1779 ce
->ce_file
= add (cp
, ce
->ce_file
);
1781 } else if (*file
== NULL
) {
1783 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1784 adios(NULL
, "unable to create temporary file in %s",
1787 ce
->ce_file
= mh_xstrdup(tempfile
);
1790 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1791 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1795 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1796 adios (NULL
, "internal error(1)");
1798 buffer
= mh_xmalloc (len
+ 1);
1801 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1802 content_error (ct
->c_file
, ct
, "unable to open for reading");
1808 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1811 switch (cc
= read (fd
, cp
, len
)) {
1813 content_error (ct
->c_file
, ct
, "error reading from");
1817 content_error (NULL
, ct
, "premature eof");
1828 /* decodeBase64() requires null-terminated input. */
1831 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1832 ct
->c_digested
? digest
: NULL
) == OK
) {
1834 unsigned char *decoded_p
= decoded
;
1835 for (i
= 0; i
< decoded_len
; ++i
) {
1836 putc (*decoded_p
++, ce
->ce_fp
);
1839 if (ferror (ce
->ce_fp
)) {
1840 content_error (ce
->ce_file
, ct
, "error writing to");
1844 if (ct
->c_digested
) {
1845 if (memcmp(digest
, ct
->c_digest
,
1846 sizeof(digest
) / sizeof(digest
[0]))) {
1847 content_error (NULL
, ct
,
1848 "content integrity suspect (digest mismatch) -- continuing");
1851 fprintf (stderr
, "content integrity confirmed\n");
1859 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1861 if (fflush (ce
->ce_fp
)) {
1862 content_error (ce
->ce_file
, ct
, "error writing to");
1866 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1869 *file
= ce
->ce_file
;
1875 return fileno (ce
->ce_fp
);
1882 free_encoding (ct
, 0);
1892 static char hex2nib
[0x80] = {
1893 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
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, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1900 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1901 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1915 return init_encoding (ct
, openQuoted
);
1920 openQuoted (CT ct
, char **file
)
1922 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1928 CE ce
= &ct
->c_cefile
;
1929 /* sbeck -- handle suffixes */
1934 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1939 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1940 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1946 if (*file
== NULL
) {
1949 ce
->ce_file
= mh_xstrdup(*file
);
1953 /* sbeck@cise.ufl.edu -- handle suffixes */
1955 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1956 if (ce
->ce_unlink
) {
1957 /* Create temporary file with filename extension. */
1958 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1959 adios(NULL
, "unable to create temporary file in %s",
1963 ce
->ce_file
= add (cp
, ce
->ce_file
);
1965 } else if (*file
== NULL
) {
1967 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1968 adios(NULL
, "unable to create temporary file in %s",
1971 ce
->ce_file
= mh_xstrdup(tempfile
);
1974 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1975 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1979 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1980 adios (NULL
, "internal error(2)");
1983 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1984 content_error (ct
->c_file
, ct
, "unable to open for reading");
1990 if ((digested
= ct
->c_digested
))
1991 MD5Init (&mdContext
);
1998 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2000 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2001 content_error (NULL
, ct
, "premature eof");
2005 if ((cc
= gotlen
) > len
)
2009 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2010 if (!isspace ((unsigned char) *ep
))
2014 for (; cp
< ep
; cp
++) {
2016 /* in an escape sequence */
2018 /* at byte 1 of an escape sequence */
2019 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2020 /* next is byte 2 */
2023 /* at byte 2 of an escape sequence */
2025 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2026 putc (mask
, ce
->ce_fp
);
2028 MD5Update (&mdContext
, &mask
, 1);
2029 if (ferror (ce
->ce_fp
)) {
2030 content_error (ce
->ce_file
, ct
, "error writing to");
2033 /* finished escape sequence; next may be literal or a new
2034 * escape sequence */
2037 /* on to next byte */
2041 /* not in an escape sequence */
2043 /* starting an escape sequence, or invalid '='? */
2044 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2045 /* "=\n" soft line break, eat the \n */
2049 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2050 /* We don't have 2 bytes left, so this is an invalid
2051 * escape sequence; just show the raw bytes (below). */
2052 } else if (isxdigit ((unsigned char) cp
[1]) &&
2053 isxdigit ((unsigned char) cp
[2])) {
2054 /* Next 2 bytes are hex digits, making this a valid escape
2055 * sequence; let's decode it (above). */
2059 /* One or both of the next 2 is out of range, making this
2060 * an invalid escape sequence; just show the raw bytes
2065 /* Just show the raw byte. */
2066 putc (*cp
, ce
->ce_fp
);
2069 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2071 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2074 if (ferror (ce
->ce_fp
)) {
2075 content_error (ce
->ce_file
, ct
, "error writing to");
2081 content_error (NULL
, ct
,
2082 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2086 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2088 if (fflush (ce
->ce_fp
)) {
2089 content_error (ce
->ce_file
, ct
, "error writing to");
2094 unsigned char digest
[16];
2096 MD5Final (digest
, &mdContext
);
2097 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2098 sizeof(digest
) / sizeof(digest
[0])))
2099 content_error (NULL
, ct
,
2100 "content integrity suspect (digest mismatch) -- continuing");
2103 fprintf (stderr
, "content integrity confirmed\n");
2106 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2109 *file
= ce
->ce_file
;
2115 return fileno (ce
->ce_fp
);
2118 free_encoding (ct
, 0);
2135 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2138 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2144 open7Bit (CT ct
, char **file
)
2146 int cc
, fd
, len
, own_ct_fp
= 0;
2147 char buffer
[BUFSIZ
];
2148 /* sbeck -- handle suffixes */
2151 CE ce
= &ct
->c_cefile
;
2154 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2159 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2160 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2166 if (*file
== NULL
) {
2169 ce
->ce_file
= mh_xstrdup(*file
);
2173 /* sbeck@cise.ufl.edu -- handle suffixes */
2175 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2176 if (ce
->ce_unlink
) {
2177 /* Create temporary file with filename extension. */
2178 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2179 adios(NULL
, "unable to create temporary file in %s",
2183 ce
->ce_file
= add (cp
, ce
->ce_file
);
2185 } else if (*file
== NULL
) {
2187 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2188 adios(NULL
, "unable to create temporary file in %s",
2191 ce
->ce_file
= mh_xstrdup(tempfile
);
2194 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2195 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2199 if (ct
->c_type
== CT_MULTIPART
) {
2200 CI ci
= &ct
->c_ctinfo
;
2204 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2205 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2206 + 1 + strlen (ci
->ci_subtype
);
2207 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2210 fputs (buffer
, ce
->ce_fp
);
2214 if (ci
->ci_comment
) {
2215 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2216 fputs ("\n\t", ce
->ce_fp
);
2220 putc (' ', ce
->ce_fp
);
2223 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2226 fprintf (ce
->ce_fp
, "\n");
2228 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2230 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2232 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2233 fprintf (ce
->ce_fp
, "\n");
2236 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2237 adios (NULL
, "internal error(3)");
2240 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2241 content_error (ct
->c_file
, ct
, "unable to open for reading");
2247 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2249 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2251 content_error (ct
->c_file
, ct
, "error reading from");
2255 content_error (NULL
, ct
, "premature eof");
2263 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2264 advise ("open7Bit", "fwrite");
2266 if (ferror (ce
->ce_fp
)) {
2267 content_error (ce
->ce_file
, ct
, "error writing to");
2272 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2274 if (fflush (ce
->ce_fp
)) {
2275 content_error (ce
->ce_file
, ct
, "error writing to");
2279 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2282 *file
= ce
->ce_file
;
2287 return fileno (ce
->ce_fp
);
2290 free_encoding (ct
, 0);
2304 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2306 char cachefile
[BUFSIZ
];
2309 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2314 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2315 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2321 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2322 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2323 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2324 ce
->ce_file
= mh_xstrdup(cachefile
);
2328 admonish (cachefile
, "unable to fopen for reading");
2331 *fd
= fileno (ce
->ce_fp
);
2335 *file
= ce
->ce_file
;
2336 *fd
= fileno (ce
->ce_fp
);
2347 return init_encoding (ct
, openFile
);
2352 openFile (CT ct
, char **file
)
2355 char cachefile
[BUFSIZ
];
2356 struct exbody
*e
= ct
->c_ctexbody
;
2357 CE ce
= &ct
->c_cefile
;
2359 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2371 content_error (NULL
, ct
, "missing name parameter");
2375 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2378 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2379 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2383 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2384 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2385 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2389 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2390 if ((fp
= fopen (cachefile
, "w"))) {
2392 char buffer
[BUFSIZ
];
2393 FILE *gp
= ce
->ce_fp
;
2395 fseek (gp
, 0L, SEEK_SET
);
2397 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2399 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2400 advise ("openFile", "fwrite");
2405 admonish (ce
->ce_file
, "error reading");
2406 (void) m_unlink (cachefile
);
2410 admonish (cachefile
, "error writing");
2411 (void) m_unlink (cachefile
);
2418 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2419 *file
= ce
->ce_file
;
2420 return fileno (ce
->ce_fp
);
2430 return init_encoding (ct
, openFTP
);
2435 openFTP (CT ct
, char **file
)
2437 int cachetype
, caching
, fd
;
2439 char *bp
, *ftp
, *user
, *pass
;
2440 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2442 CE ce
= &ct
->c_cefile
;
2443 static char *username
= NULL
;
2444 static char *password
= NULL
;
2448 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2454 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2465 if (!e
->eb_name
|| !e
->eb_site
) {
2466 content_error (NULL
, ct
, "missing %s parameter",
2467 e
->eb_name
? "site": "name");
2471 /* Get the buffer ready to go */
2473 buflen
= sizeof(buffer
);
2476 * Construct the query message for user
2478 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2484 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2490 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2491 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2496 if (e
->eb_size
> 0) {
2497 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2502 snprintf (bp
, buflen
, "? ");
2505 * Now, check the answer
2507 if (!read_yes_or_no_if_tty (buffer
))
2512 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2516 ruserpass (e
->eb_site
, &username
, &password
, 0);
2521 ce
->ce_unlink
= (*file
== NULL
);
2523 cachefile
[0] = '\0';
2524 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2525 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2526 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2527 if (*file
== NULL
) {
2534 ce
->ce_file
= mh_xstrdup(*file
);
2536 ce
->ce_file
= mh_xstrdup(cachefile
);
2539 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2540 adios(NULL
, "unable to create temporary file in %s",
2543 ce
->ce_file
= mh_xstrdup(tempfile
);
2546 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2547 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2552 int child_id
, i
, vecp
;
2556 vec
[vecp
++] = r1bindex (ftp
, '/');
2557 vec
[vecp
++] = e
->eb_site
;
2560 vec
[vecp
++] = e
->eb_dir
;
2561 vec
[vecp
++] = e
->eb_name
;
2562 vec
[vecp
++] = ce
->ce_file
,
2563 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2564 ? "ascii" : "binary";
2569 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2573 adios ("fork", "unable to");
2577 close (fileno (ce
->ce_fp
));
2579 fprintf (stderr
, "unable to exec ");
2585 if (pidXwait (child_id
, NULL
)) {
2586 username
= password
= NULL
;
2596 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2601 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2602 if ((fp
= fopen (cachefile
, "w"))) {
2604 FILE *gp
= ce
->ce_fp
;
2606 fseek (gp
, 0L, SEEK_SET
);
2608 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2610 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2611 advise ("openFTP", "fwrite");
2616 admonish (ce
->ce_file
, "error reading");
2617 (void) m_unlink (cachefile
);
2621 admonish (cachefile
, "error writing");
2622 (void) m_unlink (cachefile
);
2630 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2631 *file
= ce
->ce_file
;
2632 return fileno (ce
->ce_fp
);
2643 return init_encoding (ct
, openMail
);
2648 openMail (CT ct
, char **file
)
2650 int child_id
, fd
, i
, vecp
;
2652 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2653 struct exbody
*e
= ct
->c_ctexbody
;
2654 CE ce
= &ct
->c_cefile
;
2656 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2667 if (!e
->eb_server
) {
2668 content_error (NULL
, ct
, "missing server parameter");
2672 /* Get buffer ready to go */
2674 buflen
= sizeof(buffer
);
2676 /* Now, construct query message */
2677 snprintf (bp
, buflen
, "Retrieve content");
2683 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2689 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2691 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2693 /* Now, check answer */
2694 if (!read_yes_or_no_if_tty (buffer
))
2698 vec
[vecp
++] = r1bindex (mailproc
, '/');
2699 vec
[vecp
++] = e
->eb_server
;
2700 vec
[vecp
++] = "-subject";
2701 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2702 vec
[vecp
++] = "-body";
2703 vec
[vecp
++] = e
->eb_body
;
2706 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2710 advise ("fork", "unable to");
2714 execvp (mailproc
, vec
);
2715 fprintf (stderr
, "unable to exec ");
2721 if (pidXwait (child_id
, NULL
) == OK
)
2722 inform("request sent");
2726 if (*file
== NULL
) {
2728 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2729 adios(NULL
, "unable to create temporary file in %s",
2732 ce
->ce_file
= mh_xstrdup(tempfile
);
2735 ce
->ce_file
= mh_xstrdup(*file
);
2739 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2740 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2744 /* showproc is for mhshow and mhstore, though mhlist -debug
2745 * prints it, too. */
2746 mh_xfree(ct
->c_showproc
);
2747 ct
->c_showproc
= mh_xstrdup("true");
2749 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2750 *file
= ce
->ce_file
;
2751 return fileno (ce
->ce_fp
);
2762 return init_encoding (ct
, openURL
);
2767 openURL (CT ct
, char **file
)
2769 struct exbody
*e
= ct
->c_ctexbody
;
2770 CE ce
= &ct
->c_cefile
;
2771 char *urlprog
, *program
;
2772 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2773 int fd
, caching
, cachetype
;
2774 struct msgs_array args
= { 0, 0, NULL
};
2777 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2781 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2785 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2797 content_error(NULL
, ct
, "missing url parameter");
2801 ce
->ce_unlink
= (*file
== NULL
);
2803 cachefile
[0] = '\0';
2805 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2806 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2807 if (*file
== NULL
) {
2814 ce
->ce_file
= mh_xstrdup(*file
);
2816 ce
->ce_file
= mh_xstrdup(cachefile
);
2819 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2820 adios(NULL
, "unable to create temporary file in %s",
2823 ce
->ce_file
= mh_xstrdup(tempfile
);
2826 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2827 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2831 switch (child_id
= fork()) {
2833 adios ("fork", "unable to");
2837 argsplit_msgarg(&args
, urlprog
, &program
);
2838 app_msgarg(&args
, e
->eb_url
);
2839 app_msgarg(&args
, NULL
);
2840 dup2(fileno(ce
->ce_fp
), 1);
2841 close(fileno(ce
->ce_fp
));
2842 execvp(program
, args
.msgs
);
2843 fprintf(stderr
, "Unable to exec ");
2849 if (pidXwait(child_id
, NULL
)) {
2857 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2862 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2863 if ((fp
= fopen(cachefile
, "w"))) {
2865 FILE *gp
= ce
->ce_fp
;
2867 fseeko(gp
, 0, SEEK_SET
);
2869 while ((cc
= fread(buffer
, sizeof(*buffer
),
2870 sizeof(buffer
), gp
)) > 0)
2871 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2872 advise ("openURL", "fwrite");
2878 admonish(ce
->ce_file
, "error reading");
2879 (void) m_unlink (cachefile
);
2886 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2887 *file
= ce
->ce_file
;
2893 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2894 * has to be base64 decoded.
2897 readDigest (CT ct
, char *cp
)
2899 unsigned char *digest
;
2902 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2903 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2905 if (strlen ((char *) digest
) <= maxlen
) {
2906 memcpy (ct
->c_digest
, digest
, maxlen
);
2911 fprintf (stderr
, "MD5 digest=");
2912 for (i
= 0; i
< maxlen
; ++i
) {
2913 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2915 fprintf (stderr
, "\n");
2921 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2922 (int) strlen ((char *) digest
));
2932 /* Multipart parts might have content before the first subpart and/or
2933 after the last subpart that hasn't been stored anywhere else, so do
2936 get_leftover_mp_content (CT ct
, int before
/* or after */)
2938 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2940 int found_boundary
= 0;
2946 char *content
= NULL
;
2948 if (! m
) return NOTOK
;
2951 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2953 /* Isolate the beginning of this part to the beginning of the
2954 first subpart and save any content between them. */
2955 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2956 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2957 boundary
= concat ("--", m
->mp_start
, NULL
);
2959 struct part
*last_subpart
= NULL
;
2960 struct part
*subpart
;
2962 /* Go to the last subpart to get its end position. */
2963 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2964 last_subpart
= subpart
;
2967 if (last_subpart
== NULL
) return NOTOK
;
2969 /* Isolate the end of the last subpart to the end of this part
2970 and save any content between them. */
2971 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2972 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2973 boundary
= concat ("--", m
->mp_stop
, NULL
);
2976 /* Back up by 1 to pick up the newline. */
2977 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2979 /* Don't look beyond beginning of first subpart (before) or
2980 next part (after). */
2981 if (read
> max
) bufp
[read
-max
] = '\0';
2984 if (! strcmp (bufp
, boundary
)) {
2988 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2994 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2996 char *old_content
= content
;
2997 content
= concat (content
, bufp
, NULL
);
3001 ? concat ("\n", bufp
, NULL
)
3002 : concat (bufp
, NULL
);
3007 if (found_boundary
|| read
> max
) break;
3009 if (read
> max
) break;
3013 /* Skip the newline if that's all there is. */
3017 /* Remove trailing newline, except at EOF. */
3018 if ((before
|| ! feof (ct
->c_fp
)) &&
3019 (cp
= content
+ strlen (content
)) > content
&&
3024 if (strlen (content
) > 1) {
3026 m
->mp_content_before
= content
;
3028 m
->mp_content_after
= content
;
3043 ct_type_str (int type
) {
3045 case CT_APPLICATION
:
3046 return "application";
3062 return "unknown_type";
3068 ct_subtype_str (int type
, int subtype
) {
3070 case CT_APPLICATION
:
3072 case APPLICATION_OCTETS
:
3074 case APPLICATION_POSTSCRIPT
:
3075 return "postscript";
3077 return "unknown_app_subtype";
3081 case MESSAGE_RFC822
:
3083 case MESSAGE_PARTIAL
:
3085 case MESSAGE_EXTERNAL
:
3088 return "unknown_msg_subtype";
3094 case MULTI_ALTERNATE
:
3095 return "alternative";
3098 case MULTI_PARALLEL
:
3103 return "unknown_multipart_subtype";
3114 return "unknown_text_subtype";
3117 return "unknown_type";
3123 ct_str_type (const char *type
) {
3124 struct str2init
*s2i
;
3126 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3127 if (! strcasecmp (type
, s2i
->si_key
)) {
3131 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3140 ct_str_subtype (int type
, const char *subtype
) {
3144 case CT_APPLICATION
:
3145 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3146 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3150 return kv
->kv_value
;
3152 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3153 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3157 return kv
->kv_value
;
3159 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3160 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3164 return kv
->kv_value
;
3166 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3167 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3171 return kv
->kv_value
;
3178 /* Find the content type and InitFunc for the CT. */
3179 const struct str2init
*
3180 get_ct_init (int type
) {
3181 const struct str2init
*sp
;
3183 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3184 if (type
== sp
->si_val
) {
3193 ce_str (int encoding
) {
3198 return "quoted-printable";
3214 /* Find the content type and InitFunc for the content encoding method. */
3215 const struct str2init
*
3216 get_ce_method (const char *method
) {
3217 struct str2init
*sp
;
3219 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3220 if (! strcasecmp (method
, sp
->si_key
)) {
3229 * Parse a series of MIME attributes (or parameters) given a header as
3232 * Arguments include:
3234 * filename - Name of input file (for error messages)
3235 * fieldname - Name of field being processed
3236 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3237 * Updated to point to end of attributes when finished.
3238 * param_head - Pointer to head of parameter list
3239 * param_tail - Pointer to tail of parameter list
3240 * commentp - Pointer to header comment pointer (may be NULL)
3242 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3243 * DONE to indicate a benign error (minor parsing error, but the program
3248 parse_header_attrs (const char *filename
, const char *fieldname
,
3249 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3252 char *cp
= *header_attrp
;
3258 struct sectlist
*next
;
3264 struct sectlist
*sechead
;
3265 struct parmlist
*next
;
3266 } *pp
, *pp2
, *phead
= NULL
;
3268 while (*cp
== ';') {
3269 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3270 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3273 while (isspace ((unsigned char) *cp
))
3277 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3282 if (! suppress_extraneous_trailing_semicolon_warning
) {
3283 inform("extraneous trailing ';' in message %s's %s: "
3284 "parameter list", filename
, fieldname
);
3289 /* down case the attribute name */
3290 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3291 *dp
= tolower ((unsigned char) *dp
);
3293 for (up
= dp
; isspace ((unsigned char) *dp
);)
3295 if (dp
== cp
|| *dp
!= '=') {
3296 inform("invalid parameter in message %s's %s: "
3297 "field\n%*sparameter %s (error detected at offset %d)",
3298 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3303 * To handle RFC 2231, we have to deal with the following extensions:
3305 * name*=encoded-value
3306 * name*<N>=part-N-of-a-parameter-value
3307 * name*<N>*=encoded-part-N-of-a-parameter-value
3310 * If there's a * right before the equal sign, it's encoded.
3311 * If there's a * and one or more digits, then it's section N.
3313 * Remember we can have one or the other, or both. cp points to
3314 * beginning of name, up points past the last character in the
3318 for (vp
= cp
; vp
< up
; vp
++) {
3319 if (*vp
== '*' && vp
< up
- 1) {
3322 } else if (*vp
== '*' && vp
== up
- 1) {
3324 } else if (partial
) {
3325 if (isdigit((unsigned char) *vp
))
3326 index
= *vp
- '0' + index
* 10;
3328 inform("invalid parameter index in message %s's "
3329 "%s: field\n%*s(parameter %s)", filename
,
3330 fieldname
, strlen(invo_name
) + 2, "", cp
);
3339 * Break out the parameter name and value sections and allocate
3343 nameptr
= mh_xmalloc(len
+ 1);
3344 strncpy(nameptr
, cp
, len
);
3345 nameptr
[len
] = '\0';
3347 for (dp
++; isspace ((unsigned char) *dp
);)
3352 * Single quotes delimit the character set and language tag.
3353 * They are required on the first section (or a complete
3358 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3364 charset
= mh_xmalloc(len
+ 1);
3365 strncpy(charset
, dp
, len
);
3366 charset
[len
] = '\0';
3372 inform("missing charset in message %s's %s: "
3373 "field\n%*s(parameter %s)", filename
, fieldname
,
3374 strlen(invo_name
) + 2, "", nameptr
);
3380 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3387 lang
= mh_xmalloc(len
+ 1);
3388 strncpy(lang
, dp
, len
);
3395 inform("missing language tag in message %s's %s: "
3396 "field\n%*s(parameter %s)", filename
, fieldname
,
3397 strlen(invo_name
) + 2, "", nameptr
);
3407 * At this point vp should be pointing at the beginning
3408 * of the encoded value/section. Continue until we reach
3409 * the end or get whitespace. But first, calculate the
3410 * length so we can allocate the correct buffer size.
3413 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3415 if (*(vp
+ 1) == '\0' ||
3416 !isxdigit((unsigned char) *(vp
+ 1)) ||
3417 *(vp
+ 2) == '\0' ||
3418 !isxdigit((unsigned char) *(vp
+ 2))) {
3419 inform("invalid encoded sequence in message "
3420 "%s's %s: field\n%*s(parameter %s)",
3421 filename
, fieldname
, strlen(invo_name
) + 2,
3433 up
= valptr
= mh_xmalloc(len
+ 1);
3435 for (vp
= dp
; istoken(*vp
); vp
++) {
3437 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3448 * A "normal" string. If it's got a leading quote, then we
3449 * strip the quotes out. Otherwise go until we reach the end
3450 * or get whitespace. Note we scan it twice; once to get the
3451 * length, then the second time copies it into the destination
3458 for (cp
= dp
+ 1;;) {
3462 inform("invalid quoted-string in message %s's %s: "
3463 "field\n%*s(parameter %s)", filename
,
3464 fieldname
, strlen(invo_name
) + 2, "", nameptr
);
3484 for (cp
= dp
; istoken (*cp
); cp
++) {
3489 valptr
= mh_xmalloc(len
+ 1);
3493 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3501 strncpy(valptr
, cp
= dp
, len
);
3509 * If 'partial' is set, we don't allocate a parameter now. We
3510 * put it on the parameter linked list to be reassembled later.
3512 * "phead" points to a list of all parameters we need to reassemble.
3513 * Each parameter has a list of sections. We insert the sections in
3518 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3519 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3534 * Insert this into the section linked list
3542 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3543 sp
->next
= pp
->sechead
;
3546 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3547 if (sp2
->index
== sp
->index
) {
3548 inform("duplicate index (%d) in message "
3549 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3550 filename
, fieldname
, strlen(invo_name
) + 2, "",
3554 if (sp2
->index
< sp
->index
&&
3555 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3556 sp
->next
= sp2
->next
;
3563 inform("Internal error: cannot insert partial "
3564 "param in message %s's %s: field\n%*s(parameter %s)",
3565 filename
, fieldname
, strlen(invo_name
) + 2, "",
3572 * Save our charset and lang tags.
3575 if (index
== 0 && encoded
) {
3576 mh_xfree(pp
->charset
);
3577 pp
->charset
= charset
;
3582 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3583 pm
->pm_charset
= charset
;
3587 while (isspace ((unsigned char) *cp
))
3591 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3597 * Now that we're done, reassemble all of the partial parameters.
3600 for (pp
= phead
; pp
!= NULL
; ) {
3604 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3605 if (sp
->index
!= pindex
++) {
3606 inform("missing section %d for parameter in "
3607 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3608 filename
, fieldname
, strlen(invo_name
) + 2, "",
3615 p
= q
= mh_xmalloc(tlen
+ 1);
3616 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3617 memcpy(q
, sp
->value
, sp
->len
);
3627 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3628 pm
->pm_charset
= pp
->charset
;
3629 pm
->pm_lang
= pp
->lang
;
3640 * Return the charset for a particular content type.
3644 content_charset (CT ct
) {
3645 char *ret_charset
= NULL
;
3647 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3649 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3654 * Create a string based on a list of output parameters. Assume that this
3655 * parameter string will be appended to an existing header, so start out
3656 * with the separator (;). Perform RFC 2231 encoding when necessary.
3660 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3662 char *paramout
= NULL
;
3663 char line
[CPERLIN
* 2], *q
;
3664 int curlen
, index
, cont
, encode
, i
;
3665 size_t valoff
, numchars
;
3667 while (params
!= NULL
) {
3673 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3676 if (strlen(params
->pm_name
) > CPERLIN
) {
3677 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3682 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3685 * Loop until we get a parameter that fits within a line. We
3686 * assume new lines start with a tab, so check our overflow based
3696 * At this point we're definitely continuing the line, so
3697 * be sure to include the parameter name and section index.
3700 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3701 params
->pm_name
, index
);
3704 * Both of these functions do a NUL termination
3708 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3709 numchars
, valoff
, index
);
3711 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3721 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3726 * "line" starts with a ;\n\t, so that doesn't count against
3727 * the length. But add 8 since it starts with a tab; that's
3728 * how we end up with 5.
3731 initialwidth
= strlen(line
) + 5;
3734 * At this point the line should be built, so add it to our
3735 * current output buffer.
3738 paramout
= add(line
, paramout
);
3742 * If this won't fit on the line, start a new one. Save room in
3743 * case we need a semicolon on the end
3746 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3758 * At this point, we're either finishing a contined parameter, or
3759 * we're working on a new one.
3763 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3764 params
->pm_name
, index
);
3766 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3771 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3772 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3774 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3775 strlen(params
->pm_value
+ valoff
), valoff
);
3782 paramout
= add(line
, paramout
);
3783 initialwidth
+= strlen(line
);
3785 params
= params
->pm_next
;
3789 *offsetout
= initialwidth
;
3795 * Calculate the size of a parameter.
3799 * pm - The parameter being output
3800 * index - If continuing the parameter, the index of the section
3802 * valueoff - The current offset into the parameter value that we're
3803 * working on (previous sections have consumed valueoff bytes).
3804 * encode - Set if we should perform encoding on this parameter section
3805 * (given that we're consuming bytesfit bytes).
3806 * cont - Set if the remaining data in value will not fit on a single
3807 * line and will need to be continued.
3808 * bytesfit - The number of bytes that we can consume from the parameter
3809 * value and still fit on a completely new line. The
3810 * calculation assumes the new line starts with a tab,
3811 * includes the parameter name and any encoding, and fits
3812 * within CPERLIN bytes. Will always be at least 1.
3816 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3819 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3820 size_t len
= 0, fit
= 0;
3821 int fitlimit
= 0, eightbit
, maxfit
;
3826 * Add up the length. First, start with the parameter name.
3829 len
= strlen(pm
->pm_name
);
3832 * Scan the parameter value and see if we need to do encoding for this
3836 eightbit
= contains8bit(start
, NULL
);
3839 * Determine if we need to encode this section. Encoding is necessary if:
3841 * - There are any 8-bit characters at all and we're on the first
3843 * - There are 8-bit characters within N bytes of our section start.
3844 * N is calculated based on the number of bytes it would take to
3845 * reach CPERLIN. Specifically:
3846 * 8 (starting tab) +
3847 * strlen(param name) +
3848 * 4 ('* for section marker, '=', opening/closing '"')
3850 * is the number of bytes used by everything that isn't part of the
3851 * value. So that gets subtracted from CPERLIN.
3854 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3855 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3856 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3860 len
++; /* Add in equal sign */
3864 * We're using maxfit as a marker for how many characters we can
3865 * fit into the line. Bump it by two because we're not using quotes
3872 * If we don't have a charset or language tag in this parameter,
3876 if (! pm
->pm_charset
) {
3877 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3878 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3879 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3880 "local character set is US-ASCII", pm
->pm_name
);
3883 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3885 len
++; /* For the encoding marker */
3888 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3893 * We know we definitely need to include an index. maxfit already
3894 * includes the section marker.
3896 len
+= strlen(indexchar
);
3898 for (p
= start
; *p
!= '\0'; p
++) {
3899 if (isparamencode(*p
)) {
3907 * Just so there's no confusion: maxfit is counting OUTPUT
3908 * characters (post-encoding). fit is counting INPUT characters.
3910 if (! fitlimit
&& maxfit
>= 0)
3912 else if (! fitlimit
)
3917 * Calculate the string length, but add room for quoting \
3918 * and " if necessary. Also account for quotes at beginning
3921 for (p
= start
; *p
!= '\0'; p
++) {
3932 if (! fitlimit
&& maxfit
>= 0)
3934 else if (! fitlimit
)
3951 * Output an encoded parameter string.
3955 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3956 size_t valueoff
, int index
)
3958 size_t outlen
= 0, n
;
3959 char *endptr
= output
+ len
, *p
;
3962 * First, output the marker for an encoded string.
3970 * If the index is 0, output the character set and language tag.
3971 * If theses were NULL, they should have already been filled in
3976 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3980 if (output
> endptr
) {
3981 inform("Internal error: parameter buffer overflow");
3987 * Copy over the value, encoding if necessary
3990 p
= pm
->pm_value
+ valueoff
;
3991 while (valuelen
-- > 0) {
3992 if (isparamencode(*p
)) {
3993 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4000 if (output
> endptr
) {
4001 inform("Internal error: parameter buffer overflow");
4012 * Output a "normal" parameter, without encoding. Be sure to escape
4013 * quotes and backslashes if necessary.
4017 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4021 char *endptr
= output
+ len
, *p
;
4027 p
= pm
->pm_value
+ valueoff
;
4029 while (valuelen
-- > 0) {
4040 if (output
> endptr
) {
4041 inform("Internal error: parameter buffer overflow");
4046 if (output
- 2 > endptr
) {
4047 inform("Internal error: parameter buffer overflow");
4058 * Add a parameter to the parameter linked list
4062 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4067 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4068 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4071 (*last
)->pm_next
= pm
;
4082 * Either replace a current parameter with a new value, or add the parameter
4083 * to the parameter linked list.
4087 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4091 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4092 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4094 * If nocopy is set, it's assumed that we own both name
4095 * and value. We don't need name, so we discard it now.
4100 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4105 return add_param(first
, last
, name
, value
, nocopy
);
4109 * Retrieve a parameter value from a parameter linked list. If the parameter
4110 * value needs converted to the local character set, do that now.
4114 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4116 while (first
!= NULL
) {
4117 if (strcasecmp(name
, first
->pm_name
) == 0) {
4119 return first
->pm_value
;
4120 return getcpy(get_param_value(first
, replace
));
4122 first
= first
->pm_next
;
4129 * Return a parameter value, converting to the local character set if
4133 char *get_param_value(PM pm
, char replace
)
4135 static char buffer
[4096]; /* I hope no parameters are larger */
4136 size_t bufsize
= sizeof(buffer
);
4141 ICONV_CONST
char *p
;
4142 #else /* HAVE_ICONV */
4144 #endif /* HAVE_ICONV */
4149 * If we don't have a character set indicated, it's assumed to be
4150 * US-ASCII. If it matches our character set, we don't need to convert
4154 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4155 strlen(pm
->pm_charset
))) {
4156 return pm
->pm_value
;
4160 * In this case, we need to convert. If we have iconv support, use
4161 * that. Otherwise, go through and simply replace every non-ASCII
4162 * character with the substitution character.
4167 bufsize
= sizeof(buffer
);
4168 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4170 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4171 if (cd
== (iconv_t
) -1) {
4175 inbytes
= strlen(pm
->pm_value
);
4179 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4180 if (errno
!= EILSEQ
) {
4185 * Reset shift state, substitute our character,
4186 * try to restart conversion.
4189 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4202 for (++p
, --inbytes
;
4203 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4222 #endif /* HAVE_ICONV */
4225 * Take everything non-ASCII and substitute the replacement character
4229 bufsize
= sizeof(buffer
);
4230 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4231 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))