3 * mhparse.c -- routines to parse the contents of MIME messages
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
16 #include <h/mhparse.h>
20 #endif /* HAVE_ICONV */
26 extern int rcachesw
; /* mhcachesbr.c */
27 extern int wcachesw
; /* mhcachesbr.c */
29 int checksw
= 0; /* check Content-MD5 field */
32 * These are for mhfixmsg to:
33 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
35 * 2) Suppress the warning about bogus multipart content, and report it.
36 * 3) Suppress the warning about extraneous trailing ';' in header parameter
37 * lists, and report it.
39 int skip_mp_cte_check
;
40 int suppress_bogus_mp_content_warning
;
42 int suppress_extraneous_trailing_semicolon_warning
;
43 int extraneous_trailing_semicolon
;
44 int suppress_multiple_mime_version_warning
= 1;
46 /* list of preferred type/subtype pairs, for -prefer */
47 char *preferred_types
[NPREFS
],
48 *preferred_subtypes
[NPREFS
];
53 * Structures for TEXT messages
55 struct k2v SubText
[] = {
56 { "plain", TEXT_PLAIN
},
57 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC-1341 */
58 { "enriched", TEXT_ENRICHED
}, /* defined in RFC-1896 */
59 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
62 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
65 * Structures for MULTIPART messages
67 struct k2v SubMultiPart
[] = {
68 { "mixed", MULTI_MIXED
},
69 { "alternative", MULTI_ALTERNATE
},
70 { "digest", MULTI_DIGEST
},
71 { "parallel", MULTI_PARALLEL
},
72 { "related", MULTI_RELATED
},
73 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
77 * Structures for MESSAGE messages
79 struct k2v SubMessage
[] = {
80 { "rfc822", MESSAGE_RFC822
},
81 { "partial", MESSAGE_PARTIAL
},
82 { "external-body", MESSAGE_EXTERNAL
},
83 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
87 * Structure for APPLICATION messages
89 struct k2v SubApplication
[] = {
90 { "octet-stream", APPLICATION_OCTETS
},
91 { "postscript", APPLICATION_POSTSCRIPT
},
92 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
96 * Mapping of names of CTE types in mhbuild directives
98 static struct k2v EncodingType
[] = {
101 { "q-p", CE_QUOTED
},
102 { "quoted-printable", CE_QUOTED
},
103 { "b64", CE_BASE64
},
104 { "base64", CE_BASE64
},
110 int find_cache (CT
, int, int *, char *, char *, int);
114 int type_ok (CT
, int);
115 void content_error (char *, CT
, char *, ...);
118 void free_encoding (CT
, int);
123 static CT
get_content (FILE *, char *, int);
124 static int get_comment (const char *, const char *, char **, char **);
126 static int InitGeneric (CT
);
127 static int InitText (CT
);
128 static int InitMultiPart (CT
);
129 static void reverse_parts (CT
);
130 static void prefer_parts(CT ct
);
131 static int InitMessage (CT
);
132 static int InitApplication (CT
);
133 static int init_encoding (CT
, OpenCEFunc
);
134 static unsigned long size_encoding (CT
);
135 static int InitBase64 (CT
);
136 static int openBase64 (CT
, char **);
137 static int InitQuoted (CT
);
138 static int openQuoted (CT
, char **);
139 static int Init7Bit (CT
);
140 static int openExternal (CT
, CT
, CE
, char **, int *);
141 static int InitFile (CT
);
142 static int openFile (CT
, char **);
143 static int InitFTP (CT
);
144 static int openFTP (CT
, char **);
145 static int InitMail (CT
);
146 static int openMail (CT
, char **);
147 static int readDigest (CT
, char *);
148 static int get_leftover_mp_content (CT
, int);
149 static int InitURL (CT
);
150 static int openURL (CT
, char **);
151 static int parse_header_attrs (const char *, const char *, char **, PM
*,
153 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
154 static size_t encode_param(PM
, char *, size_t, size_t, size_t, int);
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 * Check if file is actually standard input
214 if ((is_stdin
= !(strcmp (file
, "-")))) {
215 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
217 advise("mhparse", "unable to create temporary file in %s",
221 file
= add (tfile
, NULL
);
223 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
224 if (fwrite(buffer
, 1, n
, fp
) != n
) {
225 (void) m_unlink (file
);
226 advise (file
, "error copying to temporary file");
232 if (ferror (stdin
)) {
233 (void) m_unlink (file
);
234 advise ("stdin", "error reading");
238 (void) m_unlink (file
);
239 advise (file
, "error writing");
242 fseek (fp
, 0L, SEEK_SET
);
243 } else if ((fp
= fopen (file
, "r")) == NULL
) {
244 advise (file
, "unable to read");
248 if (!(ct
= get_content (fp
, file
, 1))) {
250 (void) m_unlink (file
);
251 advise (NULL
, "unable to decode %s", file
);
256 ct
->c_unlink
= 1; /* temp file to remove */
260 if (ct
->c_end
== 0L) {
261 fseek (fp
, 0L, SEEK_END
);
262 ct
->c_end
= ftell (fp
);
265 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
277 * Main routine for reading/parsing the headers
278 * of a message content.
280 * toplevel = 1 # we are at the top level of the message
281 * toplevel = 0 # we are inside message type or multipart type
282 * # other than multipart/digest
283 * toplevel = -1 # we are inside multipart/digest
284 * NB: on failure we will fclose(in)!
288 get_content (FILE *in
, char *file
, int toplevel
)
291 char buf
[BUFSIZ
], name
[NAMESZ
];
295 m_getfld_state_t gstate
= 0;
297 /* allocate the content structure */
298 if (!(ct
= (CT
) mh_xcalloc (1, sizeof(*ct
))))
299 adios (NULL
, "out of memory");
302 ct
->c_file
= add (file
, NULL
);
303 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
306 * Parse the header fields for this
307 * content into a linked list.
309 m_getfld_track_filepos (&gstate
, in
);
310 for (compnum
= 1;;) {
311 int bufsz
= sizeof buf
;
312 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
317 /* get copies of the buffers */
318 np
= add (name
, NULL
);
319 vp
= add (buf
, NULL
);
321 /* if necessary, get rest of field */
322 while (state
== FLDPLUS
) {
324 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
325 vp
= add (buf
, vp
); /* add to previous value */
328 /* Now add the header data to the list */
329 add_header (ct
, np
, vp
);
331 /* continue, to see if this isn't the last header field */
332 ct
->c_begin
= ftell (in
) + 1;
336 ct
->c_begin
= ftell (in
) - strlen (buf
);
340 ct
->c_begin
= ftell (in
);
345 adios (NULL
, "message format error in component #%d", compnum
);
348 adios (NULL
, "getfld() returned %d", state
);
351 /* break out of the loop */
354 m_getfld_state_destroy (&gstate
);
357 * Read the content headers. We will parse the
358 * MIME related header fields into their various
359 * structures and set internal flags related to
360 * content type/subtype, etc.
363 hp
= ct
->c_first_hf
; /* start at first header field */
365 /* Get MIME-Version field */
366 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
371 vrsn
= add (hp
->value
, NULL
);
373 /* Now, cleanup this field */
376 while (isspace ((unsigned char) *cp
))
378 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
380 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
381 if (!isspace ((unsigned char) *dp
))
385 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
388 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
391 for (dp
= cp
; istoken (*dp
); dp
++)
395 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
398 admonish (NULL
, "message %s has unknown value for %s: field (%s)",
399 ct
->c_file
, VRSN_FIELD
, cp
);
404 if (! suppress_multiple_mime_version_warning
)
405 advise (NULL
, "message %s has multiple %s: fields",
406 ct
->c_file
, VRSN_FIELD
);
410 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
411 /* Get Content-Type field */
412 struct str2init
*s2i
;
413 CI ci
= &ct
->c_ctinfo
;
415 /* Check if we've already seen a Content-Type header */
417 advise (NULL
, "message %s has multiple %s: fields",
418 ct
->c_file
, TYPE_FIELD
);
422 /* Parse the Content-Type field */
423 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
427 * Set the Init function and the internal
428 * flag for this content type.
430 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
431 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
433 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
435 ct
->c_type
= s2i
->si_val
;
436 ct
->c_ctinitfnx
= s2i
->si_init
;
438 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
439 /* Get Content-Transfer-Encoding field */
441 struct str2init
*s2i
;
444 * Check if we've already seen the
445 * Content-Transfer-Encoding field
448 advise (NULL
, "message %s has multiple %s: fields",
449 ct
->c_file
, ENCODING_FIELD
);
453 /* get copy of this field */
454 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
456 while (isspace ((unsigned char) *cp
))
458 for (dp
= cp
; istoken (*dp
); dp
++)
464 * Find the internal flag and Init function
465 * for this transfer encoding.
467 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
468 if (!strcasecmp (cp
, s2i
->si_key
))
470 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
473 ct
->c_encoding
= s2i
->si_val
;
475 /* Call the Init function for this encoding */
476 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
479 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
480 /* Get Content-MD5 field */
486 if (ct
->c_digested
) {
487 advise (NULL
, "message %s has multiple %s: fields",
488 ct
->c_file
, MD5_FIELD
);
492 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
494 while (isspace ((unsigned char) *cp
))
496 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
498 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
499 if (!isspace ((unsigned char) *dp
))
503 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
506 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
511 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
519 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
520 /* Get Content-ID field */
521 ct
->c_id
= add (hp
->value
, ct
->c_id
);
523 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
524 /* Get Content-Description field */
525 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
527 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
528 /* Get Content-Disposition field */
529 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
534 hp
= hp
->next
; /* next header field */
538 * Check if we saw a Content-Type field.
539 * If not, then assign a default value for
540 * it, and the Init function.
544 * If we are inside a multipart/digest message,
545 * so default type is message/rfc822
548 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
550 ct
->c_type
= CT_MESSAGE
;
551 ct
->c_ctinitfnx
= InitMessage
;
554 * Else default type is text/plain
556 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
558 ct
->c_type
= CT_TEXT
;
559 ct
->c_ctinitfnx
= InitText
;
563 /* Use default Transfer-Encoding, if necessary */
565 ct
->c_encoding
= CE_7BIT
;
578 * small routine to add header field to list
582 add_header (CT ct
, char *name
, char *value
)
586 /* allocate header field structure */
587 hp
= mh_xmalloc (sizeof(*hp
));
589 /* link data into header structure */
594 /* link header structure into the list */
595 if (ct
->c_first_hf
== NULL
) {
596 ct
->c_first_hf
= hp
; /* this is the first */
599 ct
->c_last_hf
->next
= hp
; /* add it to the end */
608 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
609 * directives. Fills in the information of the CTinfo structure.
612 get_ctinfo (char *cp
, CT ct
, int magic
)
621 /* store copy of Content-Type line */
622 cp
= ct
->c_ctline
= add (cp
, NULL
);
624 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
627 /* change newlines to spaces */
628 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
631 /* trim trailing spaces */
632 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
633 if (!isspace ((unsigned char) *dp
))
638 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
640 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
641 &ci
->ci_comment
) == NOTOK
)
644 for (dp
= cp
; istoken (*dp
); dp
++)
647 ci
->ci_type
= add (cp
, NULL
); /* store content type */
651 advise (NULL
, "invalid %s: field in message %s (empty type)",
652 TYPE_FIELD
, ct
->c_file
);
656 /* down case the content type string */
657 for (dp
= ci
->ci_type
; *dp
; dp
++)
658 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
659 *dp
= tolower ((unsigned char) *dp
);
661 while (isspace ((unsigned char) *cp
))
664 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
665 &ci
->ci_comment
) == NOTOK
)
670 ci
->ci_subtype
= add ("", NULL
);
675 while (isspace ((unsigned char) *cp
))
678 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
679 &ci
->ci_comment
) == NOTOK
)
682 for (dp
= cp
; istoken (*dp
); dp
++)
685 ci
->ci_subtype
= add (cp
, NULL
); /* store the content subtype */
688 if (!*ci
->ci_subtype
) {
690 "invalid %s: field in message %s (empty subtype for \"%s\")",
691 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
695 /* down case the content subtype string */
696 for (dp
= ci
->ci_subtype
; *dp
; dp
++)
697 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
698 *dp
= tolower ((unsigned char) *dp
);
701 while (isspace ((unsigned char) *cp
))
704 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
705 &ci
->ci_comment
) == NOTOK
)
708 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
709 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
710 &ci
->ci_comment
)) != OK
) {
711 return status
== NOTOK
? NOTOK
: OK
;
715 * Get any <Content-Id> given in buffer
717 if (magic
&& *cp
== '<') {
722 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
723 advise (NULL
, "invalid ID in message %s", ct
->c_file
);
729 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
735 while (isspace ((unsigned char) *cp
))
740 * Get any [Content-Description] given in buffer.
742 if (magic
&& *cp
== '[') {
744 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
748 advise (NULL
, "invalid description in message %s", ct
->c_file
);
756 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
762 while (isspace ((unsigned char) *cp
))
767 * Get any {Content-Disposition} given in buffer.
769 if (magic
&& *cp
== '{') {
771 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
775 advise (NULL
, "invalid disposition in message %s", ct
->c_file
);
783 if (get_dispo(cp
, ct
, 1) != OK
)
789 while (isspace ((unsigned char) *cp
))
794 * Get any extension directives (right now just the content transfer
795 * encoding, but maybe others) that we care about.
798 if (magic
&& *cp
== '*') {
800 * See if it's a CTE we match on
805 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
809 advise (NULL
, "invalid null transfer encoding specification");
816 ct
->c_reqencoding
= CE_UNKNOWN
;
818 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
819 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
820 ct
->c_reqencoding
= kv
->kv_value
;
825 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
826 advise (NULL
, "invalid CTE specification: \"%s\"", dp
);
830 while (isspace ((unsigned char) *cp
))
835 * Check if anything is left over
839 ci
->ci_magic
= add (cp
, NULL
);
841 /* If there is a Content-Disposition header and it doesn't
842 have a *filename=, extract it from the magic contents.
843 The r1bindex call skips any leading directory
845 if (ct
->c_dispo_type
&&
846 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
847 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
848 r1bindex(ci
->ci_magic
, '/'), 0);
853 "extraneous information in message %s's %s: field\n%*s(%s)",
854 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
862 * Parse out a Content-Disposition header. A lot of this is cribbed from
866 get_dispo (char *cp
, CT ct
, int buildflag
)
868 char *dp
, *dispoheader
;
873 * Save the whole copy of the Content-Disposition header, unless we're
874 * processing a mhbuild directive. A NULL c_dispo will be a flag to
875 * mhbuild that the disposition header needs to be generated at that
879 dispoheader
= cp
= add(cp
, NULL
);
881 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
884 /* change newlines to spaces */
885 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
888 /* trim trailing spaces */
889 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
890 if (!isspace ((unsigned char) *dp
))
895 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
897 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
903 for (dp
= cp
; istoken (*dp
); dp
++)
906 ct
->c_dispo_type
= add (cp
, NULL
); /* store disposition type */
909 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
912 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
913 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
915 if (status
== NOTOK
) {
921 "extraneous information in message %s's %s: field\n%*s(%s)",
922 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
928 ct
->c_dispo
= dispoheader
;
935 get_comment (const char *filename
, const char *fieldname
, char **ap
,
940 char c
, buffer
[BUFSIZ
], *dp
;
950 advise (NULL
, "invalid comment in message %s's %s: field",
951 filename
, fieldname
);
956 if ((c
= *cp
++) == '\0')
979 if ((dp
= *commentp
)) {
980 *commentp
= concat (dp
, " ", buffer
, NULL
);
983 *commentp
= add (buffer
, NULL
);
987 while (isspace ((unsigned char) *cp
))
998 * Handles content types audio, image, and video.
999 * There's not much to do right here.
1007 return OK
; /* not much to do here */
1018 char buffer
[BUFSIZ
];
1023 CI ci
= &ct
->c_ctinfo
;
1025 /* check for missing subtype */
1026 if (!*ci
->ci_subtype
)
1027 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1030 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1032 /* allocate text character set structure */
1033 if ((t
= (struct text
*) mh_xcalloc (1, sizeof(*t
))) == NULL
)
1034 adios (NULL
, "out of memory");
1035 ct
->c_ctparams
= (void *) t
;
1037 /* scan for charset parameter */
1038 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1039 if (!strcasecmp (pm
->pm_name
, "charset"))
1042 /* check if content specified a character set */
1044 chset
= pm
->pm_value
;
1045 t
->tx_charset
= CHARSET_SPECIFIED
;
1047 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1051 * If we can not handle character set natively,
1052 * then check profile for string to modify the
1053 * terminal or display method.
1055 * termproc is for mhshow, though mhlist -debug prints it, too.
1057 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1058 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1059 if ((cp
= context_find (buffer
)))
1060 ct
->c_termproc
= getcpy (cp
);
1072 InitMultiPart (CT ct
)
1082 struct multipart
*m
;
1083 struct part
*part
, **next
;
1084 CI ci
= &ct
->c_ctinfo
;
1089 * The encoding for multipart messages must be either
1090 * 7bit, 8bit, or binary (per RFC2045).
1092 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1093 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1094 /* Copy the Content-Transfer-Encoding header field body so we can
1095 remove any trailing whitespace and leading blanks from it. */
1096 char *cte
= add (ct
->c_celine
? ct
->c_celine
: "(null)", NULL
);
1098 bp
= cte
+ strlen (cte
) - 1;
1099 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1100 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1103 "\"%s/%s\" type in message %s must be encoded in\n"
1104 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1105 "mhfixmsg -fixcte can fix it, or\n"
1106 "manually edit the file and change the \"%s\"\n"
1107 "Content-Transfer-Encoding to one of those. For now",
1108 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1115 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1118 * Check for "boundary" parameter, which is
1119 * required for multipart messages.
1122 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1123 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1129 /* complain if boundary parameter is missing */
1132 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1133 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1137 /* allocate primary structure for multipart info */
1138 if ((m
= (struct multipart
*) mh_xcalloc (1, sizeof(*m
))) == NULL
)
1139 adios (NULL
, "out of memory");
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 advise (NULL
, "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
))
1183 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
1184 adios (NULL
, "out of memory");
1186 next
= &part
->mp_next
;
1188 if (!(p
= get_content (fp
, ct
->c_file
,
1189 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1197 fseek (fp
, pos
, SEEK_SET
);
1200 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1204 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1205 if (p
->c_end
< p
->c_begin
)
1206 p
->c_begin
= p
->c_end
;
1211 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1217 if (! suppress_bogus_mp_content_warning
) {
1218 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1220 bogus_mp_content
= 1;
1222 if (!inout
&& part
) {
1224 p
->c_end
= ct
->c_end
;
1226 if (p
->c_begin
>= p
->c_end
) {
1227 for (next
= &m
->mp_parts
; *next
!= part
;
1228 next
= &((*next
)->mp_next
))
1232 free ((char *) part
);
1237 /* reverse the order of the parts for multipart/alternative */
1238 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1244 * label all subparts with part number, and
1245 * then initialize the content of the subpart.
1250 char partnam
[BUFSIZ
];
1253 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1254 pp
= partnam
+ strlen (partnam
);
1259 for (part
= m
->mp_parts
, partnum
= 1; part
;
1260 part
= part
->mp_next
, partnum
++) {
1263 sprintf (pp
, "%d", partnum
);
1264 p
->c_partno
= add (partnam
, NULL
);
1266 /* initialize the content of the subparts */
1267 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1276 get_leftover_mp_content (ct
, 1);
1277 get_leftover_mp_content (ct
, 0);
1287 * reverse the order of the parts of a multipart/alternative,
1288 * presumably to put the "most favored" alternative first, for
1289 * ease of choosing/displaying it later on. from a mail message on
1290 * nmh-workers, from kenh:
1291 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1292 * see code in mhn that did the same thing... Acccording to the RCS
1293 * logs, that code was around from the initial checkin of mhn.c by
1294 * John Romine in 1992, which is as far back as we have."
1297 reverse_parts (CT ct
)
1299 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1303 /* Reverse the order of its parts by walking the mp_parts list
1304 and pushing each node to the front. */
1305 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1306 next
= part
->mp_next
;
1307 part
->mp_next
= m
->mp_parts
;
1313 move_preferred_part (CT ct
, char *type
, char *subtype
)
1315 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1316 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1320 /* move the matching part(s) to the head of the list: walk the
1321 * list of parts, move matching parts to a new list (maintaining
1322 * their order), and finally, concatenate the old list onto the
1329 head
->mp_next
= m
->mp_parts
;
1330 nhead
->mp_next
= NULL
;
1334 part
= head
->mp_next
;
1335 while (part
!= NULL
) {
1336 ci
= &part
->mp_part
->c_ctinfo
;
1337 if (!strcasecmp(ci
->ci_type
, type
) &&
1338 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1339 prev
->mp_next
= part
->mp_next
;
1340 part
->mp_next
= NULL
;
1341 ntail
->mp_next
= part
;
1343 part
= prev
->mp_next
;
1346 part
= prev
->mp_next
;
1349 ntail
->mp_next
= head
->mp_next
;
1350 m
->mp_parts
= nhead
->mp_next
;
1355 * move parts that match the user's preferences (-prefer) to the head
1356 * of the line. process preferences in reverse so first one given
1357 * ends up first in line
1363 for (i
= npreferred
-1; i
>= 0; i
--)
1364 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1369 /* parse_mime() arranges alternates in reverse (priority) order. This
1370 function can be used to reverse them back. This will put, for
1371 example, a text/plain part before a text/html part in a
1372 multipart/alternative part, for example, where it belongs. */
1374 reverse_alternative_parts (CT ct
) {
1375 if (ct
->c_type
== CT_MULTIPART
) {
1376 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1379 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1383 /* And call recursively on each part of a multipart. */
1384 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1385 reverse_alternative_parts (part
->mp_part
);
1398 CI ci
= &ct
->c_ctinfo
;
1400 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1402 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1403 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1407 /* check for missing subtype */
1408 if (!*ci
->ci_subtype
)
1409 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1412 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1414 switch (ct
->c_subtype
) {
1415 case MESSAGE_RFC822
:
1418 case MESSAGE_PARTIAL
:
1423 if ((p
= (struct partial
*) mh_xcalloc (1, sizeof(*p
))) == NULL
)
1424 adios (NULL
, "out of memory");
1425 ct
->c_ctparams
= (void *) p
;
1427 /* scan for parameters "id", "number", and "total" */
1428 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1429 if (!strcasecmp (pm
->pm_name
, "id")) {
1430 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1433 if (!strcasecmp (pm
->pm_name
, "number")) {
1434 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1435 || p
->pm_partno
< 1) {
1438 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1439 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1440 ct
->c_file
, TYPE_FIELD
);
1445 if (!strcasecmp (pm
->pm_name
, "total")) {
1446 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1455 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1457 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1458 ci
->ci_type
, ci
->ci_subtype
,
1459 ct
->c_file
, TYPE_FIELD
);
1465 case MESSAGE_EXTERNAL
:
1472 if ((e
= (struct exbody
*) mh_xcalloc (1, sizeof(*e
))) == NULL
)
1473 adios (NULL
, "out of memory");
1474 ct
->c_ctparams
= (void *) e
;
1477 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1478 advise (ct
->c_file
, "unable to open for reading");
1482 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1484 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1492 p
->c_ceopenfnx
= NULL
;
1493 if ((exresult
= params_external (ct
, 0)) != NOTOK
1494 && p
->c_ceopenfnx
== openMail
) {
1498 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1500 content_error (NULL
, ct
,
1501 "empty body for access-type=mail-server");
1505 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1506 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1508 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1510 adios ("failed", "fread");
1513 adios (NULL
, "unexpected EOF from fread");
1516 bp
+= cc
, size
-= cc
;
1523 p
->c_end
= p
->c_begin
;
1528 if (exresult
== NOTOK
)
1530 if (e
->eb_flags
== NOTOK
)
1533 switch (p
->c_type
) {
1538 if (p
->c_subtype
!= MESSAGE_RFC822
)
1542 e
->eb_partno
= ct
->c_partno
;
1544 (*p
->c_ctinitfnx
) (p
);
1559 params_external (CT ct
, int composing
)
1562 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1563 CI ci
= &ct
->c_ctinfo
;
1565 ct
->c_ceopenfnx
= NULL
;
1566 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1567 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1568 struct str2init
*s2i
;
1569 CT p
= e
->eb_content
;
1571 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1572 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1575 e
->eb_access
= pm
->pm_value
;
1576 e
->eb_flags
= NOTOK
;
1577 p
->c_encoding
= CE_EXTERNAL
;
1580 e
->eb_access
= s2i
->si_key
;
1581 e
->eb_flags
= s2i
->si_val
;
1582 p
->c_encoding
= CE_EXTERNAL
;
1584 /* Call the Init function for this external type */
1585 if ((*s2i
->si_init
)(p
) == NOTOK
)
1589 if (!strcasecmp (pm
->pm_name
, "name")) {
1590 e
->eb_name
= pm
->pm_value
;
1593 if (!strcasecmp (pm
->pm_name
, "permission")) {
1594 e
->eb_permission
= pm
->pm_value
;
1597 if (!strcasecmp (pm
->pm_name
, "site")) {
1598 e
->eb_site
= pm
->pm_value
;
1601 if (!strcasecmp (pm
->pm_name
, "directory")) {
1602 e
->eb_dir
= pm
->pm_value
;
1605 if (!strcasecmp (pm
->pm_name
, "mode")) {
1606 e
->eb_mode
= pm
->pm_value
;
1609 if (!strcasecmp (pm
->pm_name
, "size")) {
1610 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1613 if (!strcasecmp (pm
->pm_name
, "server")) {
1614 e
->eb_server
= pm
->pm_value
;
1617 if (!strcasecmp (pm
->pm_name
, "subject")) {
1618 e
->eb_subject
= pm
->pm_value
;
1621 if (!strcasecmp (pm
->pm_name
, "url")) {
1623 * According to RFC 2017, we have to remove all whitespace from
1627 char *u
, *p
= pm
->pm_value
;
1628 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1630 for (; *p
!= '\0'; p
++) {
1631 if (! isspace((unsigned char) *p
))
1638 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1639 e
->eb_body
= getcpy (pm
->pm_value
);
1644 if (!e
->eb_access
) {
1646 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1647 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1660 InitApplication (CT ct
)
1662 CI ci
= &ct
->c_ctinfo
;
1665 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1672 * TRANSFER ENCODINGS
1676 init_encoding (CT ct
, OpenCEFunc openfnx
)
1678 ct
->c_ceopenfnx
= openfnx
;
1679 ct
->c_ceclosefnx
= close_encoding
;
1680 ct
->c_cesizefnx
= size_encoding
;
1687 close_encoding (CT ct
)
1689 CE ce
= &ct
->c_cefile
;
1698 static unsigned long
1699 size_encoding (CT ct
)
1704 CE ce
= &ct
->c_cefile
;
1707 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1708 return (long) st
.st_size
;
1711 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1712 return (long) st
.st_size
;
1717 if (ct
->c_encoding
== CE_EXTERNAL
)
1718 return (ct
->c_end
- ct
->c_begin
);
1721 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1722 return (ct
->c_end
- ct
->c_begin
);
1724 if (fstat (fd
, &st
) != NOTOK
)
1725 size
= (long) st
.st_size
;
1729 (*ct
->c_ceclosefnx
) (ct
);
1741 return init_encoding (ct
, openBase64
);
1746 openBase64 (CT ct
, char **file
)
1749 int fd
, own_ct_fp
= 0;
1750 char *cp
, *buffer
= NULL
;
1751 /* sbeck -- handle suffixes */
1753 CE ce
= &ct
->c_cefile
;
1754 const char *decoded
;
1756 unsigned char digest
[16];
1759 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1764 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1765 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1771 if (*file
== NULL
) {
1774 ce
->ce_file
= add (*file
, NULL
);
1778 /* sbeck@cise.ufl.edu -- handle suffixes */
1780 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1781 if (ce
->ce_unlink
) {
1782 /* Create temporary file with filename extension. */
1783 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1784 adios(NULL
, "unable to create temporary file in %s",
1788 ce
->ce_file
= add (cp
, ce
->ce_file
);
1790 } else if (*file
== NULL
) {
1792 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1793 adios(NULL
, "unable to create temporary file in %s",
1796 ce
->ce_file
= add (tempfile
, NULL
);
1799 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1800 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1804 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1805 adios (NULL
, "internal error(1)");
1807 buffer
= mh_xmalloc (len
+ 1);
1810 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1811 content_error (ct
->c_file
, ct
, "unable to open for reading");
1817 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1820 switch (cc
= read (fd
, cp
, len
)) {
1822 content_error (ct
->c_file
, ct
, "error reading from");
1826 content_error (NULL
, ct
, "premature eof");
1837 /* decodeBase64() requires null-terminated input. */
1840 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1841 ct
->c_digested
? digest
: NULL
) == OK
) {
1843 const char *decoded_p
= decoded
;
1844 for (i
= 0; i
< decoded_len
; ++i
) {
1845 putc (*decoded_p
++, ce
->ce_fp
);
1847 if (ferror (ce
->ce_fp
)) {
1848 content_error (ce
->ce_file
, ct
, "error writing to");
1852 if (ct
->c_digested
) {
1853 if (memcmp(digest
, ct
->c_digest
,
1854 sizeof(digest
) / sizeof(digest
[0]))) {
1855 content_error (NULL
, ct
,
1856 "content integrity suspect (digest mismatch) -- continuing");
1859 fprintf (stderr
, "content integrity confirmed\n");
1867 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1869 if (fflush (ce
->ce_fp
)) {
1870 content_error (ce
->ce_file
, ct
, "error writing to");
1874 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1877 *file
= ce
->ce_file
;
1883 return fileno (ce
->ce_fp
);
1890 free_encoding (ct
, 0);
1900 static char hex2nib
[0x80] = {
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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1908 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1909 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1910 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1911 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1913 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1914 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1915 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1916 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1923 return init_encoding (ct
, openQuoted
);
1928 openQuoted (CT ct
, char **file
)
1930 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1936 CE ce
= &ct
->c_cefile
;
1937 /* sbeck -- handle suffixes */
1942 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1947 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1948 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1954 if (*file
== NULL
) {
1957 ce
->ce_file
= add (*file
, NULL
);
1961 /* sbeck@cise.ufl.edu -- handle suffixes */
1963 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1964 if (ce
->ce_unlink
) {
1965 /* Create temporary file with filename extension. */
1966 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1967 adios(NULL
, "unable to create temporary file in %s",
1971 ce
->ce_file
= add (cp
, ce
->ce_file
);
1973 } else if (*file
== NULL
) {
1975 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1976 adios(NULL
, "unable to create temporary file in %s",
1979 ce
->ce_file
= add (tempfile
, NULL
);
1982 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1983 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1987 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1988 adios (NULL
, "internal error(2)");
1991 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1992 content_error (ct
->c_file
, ct
, "unable to open for reading");
1998 if ((digested
= ct
->c_digested
))
1999 MD5Init (&mdContext
);
2006 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2008 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2009 content_error (NULL
, ct
, "premature eof");
2013 if ((cc
= gotlen
) > len
)
2017 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2018 if (!isspace ((unsigned char) *ep
))
2022 for (; cp
< ep
; cp
++) {
2024 /* in an escape sequence */
2026 /* at byte 1 of an escape sequence */
2027 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2028 /* next is byte 2 */
2031 /* at byte 2 of an escape sequence */
2033 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2034 putc (mask
, ce
->ce_fp
);
2036 MD5Update (&mdContext
, &mask
, 1);
2037 if (ferror (ce
->ce_fp
)) {
2038 content_error (ce
->ce_file
, ct
, "error writing to");
2041 /* finished escape sequence; next may be literal or a new
2042 * escape sequence */
2045 /* on to next byte */
2049 /* not in an escape sequence */
2051 /* starting an escape sequence, or invalid '='? */
2052 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2053 /* "=\n" soft line break, eat the \n */
2057 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2058 /* We don't have 2 bytes left, so this is an invalid
2059 * escape sequence; just show the raw bytes (below). */
2060 } else if (isxdigit ((unsigned char) cp
[1]) &&
2061 isxdigit ((unsigned char) cp
[2])) {
2062 /* Next 2 bytes are hex digits, making this a valid escape
2063 * sequence; let's decode it (above). */
2067 /* One or both of the next 2 is out of range, making this
2068 * an invalid escape sequence; just show the raw bytes
2073 /* Just show the raw byte. */
2074 putc (*cp
, ce
->ce_fp
);
2077 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2079 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2082 if (ferror (ce
->ce_fp
)) {
2083 content_error (ce
->ce_file
, ct
, "error writing to");
2089 content_error (NULL
, ct
,
2090 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2094 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2096 if (fflush (ce
->ce_fp
)) {
2097 content_error (ce
->ce_file
, ct
, "error writing to");
2102 unsigned char digest
[16];
2104 MD5Final (digest
, &mdContext
);
2105 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2106 sizeof(digest
) / sizeof(digest
[0])))
2107 content_error (NULL
, ct
,
2108 "content integrity suspect (digest mismatch) -- continuing");
2111 fprintf (stderr
, "content integrity confirmed\n");
2114 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2117 *file
= ce
->ce_file
;
2123 return fileno (ce
->ce_fp
);
2126 free_encoding (ct
, 0);
2143 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2146 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2152 open7Bit (CT ct
, char **file
)
2154 int cc
, fd
, len
, own_ct_fp
= 0;
2155 char buffer
[BUFSIZ
];
2156 /* sbeck -- handle suffixes */
2159 CE ce
= &ct
->c_cefile
;
2162 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2167 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2168 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2174 if (*file
== NULL
) {
2177 ce
->ce_file
= add (*file
, NULL
);
2181 /* sbeck@cise.ufl.edu -- handle suffixes */
2183 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2184 if (ce
->ce_unlink
) {
2185 /* Create temporary file with filename extension. */
2186 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2187 adios(NULL
, "unable to create temporary file in %s",
2191 ce
->ce_file
= add (cp
, ce
->ce_file
);
2193 } else if (*file
== NULL
) {
2195 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2196 adios(NULL
, "unable to create temporary file in %s",
2199 ce
->ce_file
= add (tempfile
, NULL
);
2202 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2203 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2207 if (ct
->c_type
== CT_MULTIPART
) {
2208 CI ci
= &ct
->c_ctinfo
;
2212 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2213 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2214 + 1 + strlen (ci
->ci_subtype
);
2215 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2218 fputs (buffer
, ce
->ce_fp
);
2222 if (ci
->ci_comment
) {
2223 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2224 fputs ("\n\t", ce
->ce_fp
);
2228 putc (' ', ce
->ce_fp
);
2231 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2234 fprintf (ce
->ce_fp
, "\n");
2236 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2238 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2240 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2241 fprintf (ce
->ce_fp
, "\n");
2244 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2245 adios (NULL
, "internal error(3)");
2248 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2249 content_error (ct
->c_file
, ct
, "unable to open for reading");
2255 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2257 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2259 content_error (ct
->c_file
, ct
, "error reading from");
2263 content_error (NULL
, ct
, "premature eof");
2271 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2272 advise ("open7Bit", "fwrite");
2274 if (ferror (ce
->ce_fp
)) {
2275 content_error (ce
->ce_file
, ct
, "error writing to");
2280 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2282 if (fflush (ce
->ce_fp
)) {
2283 content_error (ce
->ce_file
, ct
, "error writing to");
2287 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2290 *file
= ce
->ce_file
;
2295 return fileno (ce
->ce_fp
);
2298 free_encoding (ct
, 0);
2312 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2314 char cachefile
[BUFSIZ
];
2317 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2322 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2323 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2329 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2330 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2331 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2332 ce
->ce_file
= getcpy (cachefile
);
2336 admonish (cachefile
, "unable to fopen for reading");
2340 *fd
= fileno (ce
->ce_fp
);
2344 *file
= ce
->ce_file
;
2345 *fd
= fileno (ce
->ce_fp
);
2356 return init_encoding (ct
, openFile
);
2361 openFile (CT ct
, char **file
)
2364 char cachefile
[BUFSIZ
];
2365 struct exbody
*e
= ct
->c_ctexbody
;
2366 CE ce
= &ct
->c_cefile
;
2368 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2380 content_error (NULL
, ct
, "missing name parameter");
2384 ce
->ce_file
= getcpy (e
->eb_name
);
2387 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2388 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2392 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2393 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2394 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2398 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2399 if ((fp
= fopen (cachefile
, "w"))) {
2401 char buffer
[BUFSIZ
];
2402 FILE *gp
= ce
->ce_fp
;
2404 fseek (gp
, 0L, SEEK_SET
);
2406 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2408 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2409 advise ("openFile", "fwrite");
2414 admonish (ce
->ce_file
, "error reading");
2415 (void) m_unlink (cachefile
);
2419 admonish (cachefile
, "error writing");
2420 (void) m_unlink (cachefile
);
2427 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2428 *file
= ce
->ce_file
;
2429 return fileno (ce
->ce_fp
);
2439 return init_encoding (ct
, openFTP
);
2444 openFTP (CT ct
, char **file
)
2446 int cachetype
, caching
, fd
;
2448 char *bp
, *ftp
, *user
, *pass
;
2449 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2451 CE ce
= &ct
->c_cefile
;
2452 static char *username
= NULL
;
2453 static char *password
= NULL
;
2457 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2463 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2474 if (!e
->eb_name
|| !e
->eb_site
) {
2475 content_error (NULL
, ct
, "missing %s parameter",
2476 e
->eb_name
? "site": "name");
2480 /* Get the buffer ready to go */
2482 buflen
= sizeof(buffer
);
2485 * Construct the query message for user
2487 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2493 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2499 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2500 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2505 if (e
->eb_size
> 0) {
2506 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2511 snprintf (bp
, buflen
, "? ");
2514 * Now, check the answer
2516 if (!read_yes_or_no_if_tty (buffer
))
2521 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2525 ruserpass (e
->eb_site
, &username
, &password
);
2530 ce
->ce_unlink
= (*file
== NULL
);
2532 cachefile
[0] = '\0';
2533 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2534 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2535 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2536 if (*file
== NULL
) {
2543 ce
->ce_file
= add (*file
, NULL
);
2545 ce
->ce_file
= add (cachefile
, NULL
);
2548 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2549 adios(NULL
, "unable to create temporary file in %s",
2552 ce
->ce_file
= add (tempfile
, NULL
);
2555 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2556 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2561 int child_id
, i
, vecp
;
2565 vec
[vecp
++] = r1bindex (ftp
, '/');
2566 vec
[vecp
++] = e
->eb_site
;
2569 vec
[vecp
++] = e
->eb_dir
;
2570 vec
[vecp
++] = e
->eb_name
;
2571 vec
[vecp
++] = ce
->ce_file
,
2572 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2573 ? "ascii" : "binary";
2578 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2582 adios ("fork", "unable to");
2586 close (fileno (ce
->ce_fp
));
2588 fprintf (stderr
, "unable to exec ");
2594 if (pidXwait (child_id
, NULL
)) {
2595 username
= password
= NULL
;
2605 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2610 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2611 if ((fp
= fopen (cachefile
, "w"))) {
2613 FILE *gp
= ce
->ce_fp
;
2615 fseek (gp
, 0L, SEEK_SET
);
2617 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2619 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2620 advise ("openFTP", "fwrite");
2625 admonish (ce
->ce_file
, "error reading");
2626 (void) m_unlink (cachefile
);
2630 admonish (cachefile
, "error writing");
2631 (void) m_unlink (cachefile
);
2639 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2640 *file
= ce
->ce_file
;
2641 return fileno (ce
->ce_fp
);
2652 return init_encoding (ct
, openMail
);
2657 openMail (CT ct
, char **file
)
2659 int child_id
, fd
, i
, vecp
;
2661 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2662 struct exbody
*e
= ct
->c_ctexbody
;
2663 CE ce
= &ct
->c_cefile
;
2665 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2676 if (!e
->eb_server
) {
2677 content_error (NULL
, ct
, "missing server parameter");
2681 /* Get buffer ready to go */
2683 buflen
= sizeof(buffer
);
2685 /* Now, construct query message */
2686 snprintf (bp
, buflen
, "Retrieve content");
2692 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2698 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2700 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2702 /* Now, check answer */
2703 if (!read_yes_or_no_if_tty (buffer
))
2707 vec
[vecp
++] = r1bindex (mailproc
, '/');
2708 vec
[vecp
++] = e
->eb_server
;
2709 vec
[vecp
++] = "-subject";
2710 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2711 vec
[vecp
++] = "-body";
2712 vec
[vecp
++] = e
->eb_body
;
2715 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2719 advise ("fork", "unable to");
2723 execvp (mailproc
, vec
);
2724 fprintf (stderr
, "unable to exec ");
2730 if (pidXwait (child_id
, NULL
) == OK
)
2731 advise (NULL
, "request sent");
2735 if (*file
== NULL
) {
2737 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2738 adios(NULL
, "unable to create temporary file in %s",
2741 ce
->ce_file
= add (tempfile
, NULL
);
2744 ce
->ce_file
= add (*file
, NULL
);
2748 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2749 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2753 /* showproc is for mhshow and mhstore, though mhlist -debug
2754 * prints it, too. */
2756 free (ct
->c_showproc
);
2757 ct
->c_showproc
= add ("true", NULL
);
2759 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2760 *file
= ce
->ce_file
;
2761 return fileno (ce
->ce_fp
);
2772 return init_encoding (ct
, openURL
);
2777 openURL (CT ct
, char **file
)
2779 struct exbody
*e
= ct
->c_ctexbody
;
2780 CE ce
= &ct
->c_cefile
;
2781 char *urlprog
, *program
;
2782 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2783 int fd
, caching
, cachetype
;
2784 struct msgs_array args
= { 0, 0, NULL
};
2787 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2791 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2795 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2807 content_error(NULL
, ct
, "missing url parameter");
2811 ce
->ce_unlink
= (*file
== NULL
);
2813 cachefile
[0] = '\0';
2815 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2816 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2817 if (*file
== NULL
) {
2824 ce
->ce_file
= add(*file
, NULL
);
2826 ce
->ce_file
= add(cachefile
, NULL
);
2829 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2830 adios(NULL
, "unable to create temporary file in %s",
2833 ce
->ce_file
= add (tempfile
, NULL
);
2836 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2837 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2841 switch (child_id
= fork()) {
2843 adios ("fork", "unable to");
2847 argsplit_msgarg(&args
, urlprog
, &program
);
2848 app_msgarg(&args
, e
->eb_url
);
2849 app_msgarg(&args
, NULL
);
2850 dup2(fileno(ce
->ce_fp
), 1);
2851 close(fileno(ce
->ce_fp
));
2852 execvp(program
, args
.msgs
);
2853 fprintf(stderr
, "Unable to exec ");
2859 if (pidXwait(child_id
, NULL
)) {
2867 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2872 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2873 if ((fp
= fopen(cachefile
, "w"))) {
2875 FILE *gp
= ce
->ce_fp
;
2877 fseeko(gp
, 0, SEEK_SET
);
2879 while ((cc
= fread(buffer
, sizeof(*buffer
),
2880 sizeof(buffer
), gp
)) > 0)
2881 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2882 advise ("openURL", "fwrite");
2888 admonish(ce
->ce_file
, "error reading");
2889 (void) m_unlink (cachefile
);
2896 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2897 *file
= ce
->ce_file
;
2903 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2904 * has to be base64 decoded.
2907 readDigest (CT ct
, char *cp
)
2912 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2913 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2915 if (strlen (digest
) <= maxlen
) {
2916 memcpy (ct
->c_digest
, digest
, maxlen
);
2921 fprintf (stderr
, "MD5 digest=");
2922 for (i
= 0; i
< maxlen
; ++i
) {
2923 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2925 fprintf (stderr
, "\n");
2931 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2932 (int) strlen (digest
));
2943 /* Multipart parts might have content before the first subpart and/or
2944 after the last subpart that hasn't been stored anywhere else, so do
2947 get_leftover_mp_content (CT ct
, int before
/* or after */)
2949 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2951 int found_boundary
= 0;
2957 char *content
= NULL
;
2959 if (! m
) return NOTOK
;
2962 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2964 /* Isolate the beginning of this part to the beginning of the
2965 first subpart and save any content between them. */
2966 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2967 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2968 boundary
= concat ("--", m
->mp_start
, NULL
);
2970 struct part
*last_subpart
= NULL
;
2971 struct part
*subpart
;
2973 /* Go to the last subpart to get its end position. */
2974 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2975 last_subpart
= subpart
;
2978 if (last_subpart
== NULL
) return NOTOK
;
2980 /* Isolate the end of the last subpart to the end of this part
2981 and save any content between them. */
2982 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2983 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2984 boundary
= concat ("--", m
->mp_stop
, NULL
);
2987 /* Back up by 1 to pick up the newline. */
2988 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2990 /* Don't look beyond beginning of first subpart (before) or
2991 next part (after). */
2992 if (read
> max
) bufp
[read
-max
] = '\0';
2995 if (! strcmp (bufp
, boundary
)) {
2999 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3005 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3007 char *old_content
= content
;
3008 content
= concat (content
, bufp
, NULL
);
3012 ? concat ("\n", bufp
, NULL
)
3013 : concat (bufp
, NULL
);
3018 if (found_boundary
|| read
> max
) break;
3020 if (read
> max
) break;
3024 /* Skip the newline if that's all there is. */
3028 /* Remove trailing newline, except at EOF. */
3029 if ((before
|| ! feof (ct
->c_fp
)) &&
3030 (cp
= content
+ strlen (content
)) > content
&&
3035 if (strlen (content
) > 1) {
3037 m
->mp_content_before
= content
;
3039 m
->mp_content_after
= content
;
3054 ct_type_str (int type
) {
3056 case CT_APPLICATION
:
3057 return "application";
3073 return "unknown_type";
3079 ct_subtype_str (int type
, int subtype
) {
3081 case CT_APPLICATION
:
3083 case APPLICATION_OCTETS
:
3085 case APPLICATION_POSTSCRIPT
:
3086 return "postscript";
3088 return "unknown_app_subtype";
3092 case MESSAGE_RFC822
:
3094 case MESSAGE_PARTIAL
:
3096 case MESSAGE_EXTERNAL
:
3099 return "unknown_msg_subtype";
3105 case MULTI_ALTERNATE
:
3106 return "alternative";
3109 case MULTI_PARALLEL
:
3114 return "unknown_multipart_subtype";
3125 return "unknown_text_subtype";
3128 return "unknown_type";
3134 ct_str_type (const char *type
) {
3135 struct str2init
*s2i
;
3137 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3138 if (! strcasecmp (type
, s2i
->si_key
)) {
3142 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3151 ct_str_subtype (int type
, const char *subtype
) {
3155 case CT_APPLICATION
:
3156 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3157 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3161 return kv
->kv_value
;
3163 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3164 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3168 return kv
->kv_value
;
3170 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3171 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3175 return kv
->kv_value
;
3177 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3178 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3182 return kv
->kv_value
;
3189 /* Find the content type and InitFunc for the CT. */
3190 const struct str2init
*
3191 get_ct_init (int type
) {
3192 const struct str2init
*sp
;
3194 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3195 if (type
== sp
->si_val
) {
3204 ce_str (int encoding
) {
3209 return "quoted-printable";
3225 /* Find the content type and InitFunc for the content encoding method. */
3226 const struct str2init
*
3227 get_ce_method (const char *method
) {
3228 struct str2init
*sp
;
3230 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3231 if (! strcasecmp (method
, sp
->si_key
)) {
3240 * Parse a series of MIME attributes (or parameters) given a header as
3243 * Arguments include:
3245 * filename - Name of input file (for error messages)
3246 * fieldname - Name of field being processed
3247 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3248 * Updated to point to end of attributes when finished.
3249 * param_head - Pointer to head of parameter list
3250 * param_tail - Pointer to tail of parameter list
3251 * commentp - Pointer to header comment pointer (may be NULL)
3253 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3254 * DONE to indicate a benign error (minor parsing error, but the program
3259 parse_header_attrs (const char *filename
, const char *fieldname
,
3260 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3263 char *cp
= *header_attrp
;
3269 struct sectlist
*next
;
3275 struct sectlist
*sechead
;
3276 struct parmlist
*next
;
3277 } *pp
, *pp2
, *phead
= NULL
;
3279 while (*cp
== ';') {
3280 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3281 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3284 while (isspace ((unsigned char) *cp
))
3288 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3293 if (! suppress_extraneous_trailing_semicolon_warning
) {
3295 "extraneous trailing ';' in message %s's %s: "
3297 filename
, fieldname
);
3299 extraneous_trailing_semicolon
= 1;
3303 /* down case the attribute name */
3304 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3305 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
3306 *dp
= tolower ((unsigned char) *dp
);
3308 for (up
= dp
; isspace ((unsigned char) *dp
);)
3310 if (dp
== cp
|| *dp
!= '=') {
3312 "invalid parameter in message %s's %s: "
3313 "field\n%*sparameter %s (error detected at offset %d)",
3314 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3319 * To handle RFC 2231, we have to deal with the following extensions:
3321 * name*=encoded-value
3322 * name*<N>=part-N-of-a-parameter-value
3323 * name*<N>*=encoded-part-N-of-a-parameter-value
3326 * If there's a * right before the equal sign, it's encoded.
3327 * If there's a * and one or more digits, then it's section N.
3329 * Remember we can have one or the other, or both. cp points to
3330 * beginning of name, up points past the last character in the
3334 for (vp
= cp
; vp
< up
; vp
++) {
3335 if (*vp
== '*' && vp
< up
- 1) {
3338 } else if (*vp
== '*' && vp
== up
- 1) {
3340 } else if (partial
) {
3341 if (isdigit((unsigned char) *vp
))
3342 index
= *vp
- '0' + index
* 10;
3344 advise (NULL
, "invalid parameter index in message %s's "
3345 "%s: field\n%*s(parameter %s)", filename
,
3346 fieldname
, strlen(invo_name
) + 2, "", cp
);
3355 * Break out the parameter name and value sections and allocate
3359 nameptr
= mh_xmalloc(len
+ 1);
3360 strncpy(nameptr
, cp
, len
);
3361 nameptr
[len
] = '\0';
3363 for (dp
++; isspace ((unsigned char) *dp
);)
3368 * Single quotes delimit the character set and language tag.
3369 * They are required on the first section (or a complete
3374 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3380 charset
= mh_xmalloc(len
+ 1);
3381 strncpy(charset
, dp
, len
);
3382 charset
[len
] = '\0';
3388 advise(NULL
, "missing charset in message %s's %s: "
3389 "field\n%*s(parameter %s)", filename
, fieldname
,
3390 strlen(invo_name
) + 2, "", nameptr
);
3396 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3403 lang
= mh_xmalloc(len
+ 1);
3404 strncpy(lang
, dp
, len
);
3411 advise(NULL
, "missing language tag in message %s's %s: "
3412 "field\n%*s(parameter %s)", filename
, fieldname
,
3413 strlen(invo_name
) + 2, "", nameptr
);
3424 * At this point vp should be pointing at the beginning
3425 * of the encoded value/section. Continue until we reach
3426 * the end or get whitespace. But first, calculate the
3427 * length so we can allocate the correct buffer size.
3430 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3432 if (*(vp
+ 1) == '\0' ||
3433 !isxdigit((unsigned char) *(vp
+ 1)) ||
3434 *(vp
+ 2) == '\0' ||
3435 !isxdigit((unsigned char) *(vp
+ 2))) {
3436 advise(NULL
, "invalid encoded sequence in message "
3437 "%s's %s: field\n%*s(parameter %s)",
3438 filename
, fieldname
, strlen(invo_name
) + 2,
3452 up
= valptr
= mh_xmalloc(len
+ 1);
3454 for (vp
= dp
; istoken(*vp
); vp
++) {
3456 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3467 * A "normal" string. If it's got a leading quote, then we
3468 * strip the quotes out. Otherwise go until we reach the end
3469 * or get whitespace. Note we scan it twice; once to get the
3470 * length, then the second time copies it into the destination
3477 for (cp
= dp
+ 1;;) {
3482 "invalid quoted-string in message %s's %s: "
3483 "field\n%*s(parameter %s)",
3484 filename
, fieldname
, strlen(invo_name
) + 2, "",
3507 for (cp
= dp
; istoken (*cp
); cp
++) {
3512 valptr
= mh_xmalloc(len
+ 1);
3516 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3524 strncpy(valptr
, cp
= dp
, len
);
3532 * If 'partial' is set, we don't allocate a parameter now. We
3533 * put it on the parameter linked list to be reassembled later.
3535 * "phead" points to a list of all parameters we need to reassemble.
3536 * Each parameter has a list of sections. We insert the sections in
3541 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3542 if (strcasecmp(nameptr
, pp
->name
) == 0)
3547 pp
= mh_xmalloc(sizeof(*pp
));
3548 memset(pp
, 0, sizeof(*pp
));
3555 * Insert this into the section linked list
3558 sp
= mh_xmalloc(sizeof(*sp
));
3559 memset(sp
, 0, sizeof(*sp
));
3564 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3565 sp
->next
= pp
->sechead
;
3568 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3569 if (sp2
->index
== sp
->index
) {
3570 advise (NULL
, "duplicate index (%d) in message "
3571 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3572 filename
, fieldname
, strlen(invo_name
) + 2, "",
3577 if (sp2
->index
< sp
->index
&&
3578 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3579 sp
->next
= sp2
->next
;
3586 advise(NULL
, "Internal error: cannot insert partial "
3587 "param in message %s's %s: field\n%*s(parameter %s)",
3588 filename
, fieldname
, strlen(invo_name
) + 2, "",
3596 * Save our charset and lang tags.
3599 if (index
== 0 && encoded
) {
3602 pp
->charset
= charset
;
3608 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3609 pm
->pm_charset
= charset
;
3613 while (isspace ((unsigned char) *cp
))
3617 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3623 * Now that we're done, reassemble all of the partial parameters.
3626 for (pp
= phead
; pp
!= NULL
; ) {
3630 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3631 if (sp
->index
!= pindex
++) {
3632 advise(NULL
, "missing section %d for parameter in "
3633 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3634 filename
, fieldname
, strlen(invo_name
) + 2, "",
3641 p
= q
= mh_xmalloc(tlen
+ 1);
3642 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3643 memcpy(q
, sp
->value
, sp
->len
);
3653 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3654 pm
->pm_charset
= pp
->charset
;
3655 pm
->pm_lang
= pp
->lang
;
3666 * Return the charset for a particular content type.
3670 content_charset (CT ct
) {
3671 char *ret_charset
= NULL
;
3673 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3675 return ret_charset
? ret_charset
: getcpy ("US-ASCII");
3680 * Create a string based on a list of output parameters. Assume that this
3681 * parameter string will be appended to an existing header, so start out
3682 * with the separator (;). Perform RFC 2231 encoding when necessary.
3686 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3688 char *paramout
= NULL
;
3689 char line
[CPERLIN
* 2], *q
;
3690 int curlen
, index
, cont
, encode
, i
;
3691 size_t valoff
, numchars
;
3693 while (params
!= NULL
) {
3699 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3702 if (strlen(params
->pm_name
) > CPERLIN
) {
3703 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3709 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3712 * Loop until we get a parameter that fits within a line. We
3713 * assume new lines start with a tab, so check our overflow based
3723 * At this point we're definitely continuing the line, so
3724 * be sure to include the parameter name and section index.
3727 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3728 params
->pm_name
, index
);
3731 * Both of these functions do a NUL termination
3735 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3736 numchars
, valoff
, index
);
3738 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3749 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3754 * "line" starts with a ;\n\t, so that doesn't count against
3755 * the length. But add 8 since it starts with a tab; that's
3756 * how we end up with 5.
3759 initialwidth
= strlen(line
) + 5;
3762 * At this point the line should be built, so add it to our
3763 * current output buffer.
3766 paramout
= add(line
, paramout
);
3770 * If this won't fit on the line, start a new one. Save room in
3771 * case we need a semicolon on the end
3774 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3786 * At this point, we're either finishing a contined parameter, or
3787 * we're working on a new one.
3791 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3792 params
->pm_name
, index
);
3794 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3799 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3800 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3802 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3803 strlen(params
->pm_value
+ valoff
), valoff
);
3811 paramout
= add(line
, paramout
);
3812 initialwidth
+= strlen(line
);
3814 params
= params
->pm_next
;
3818 *offsetout
= initialwidth
;
3824 * Calculate the size of a parameter.
3828 * pm - The parameter being output
3829 * index - If continuing the parameter, the index of the section
3831 * valueoff - The current offset into the parameter value that we're
3832 * working on (previous sections have consumed valueoff bytes).
3833 * encode - Set if we should perform encoding on this parameter section
3834 * (given that we're consuming bytesfit bytes).
3835 * cont - Set if the remaining data in value will not fit on a single
3836 * line and will need to be continued.
3837 * bytesfit - The number of bytes that we can consume from the parameter
3838 * value and still fit on a completely new line. The
3839 * calculation assumes the new line starts with a tab,
3840 * includes the parameter name and any encoding, and fits
3841 * within CPERLIN bytes. Will always be at least 1.
3845 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3848 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3849 size_t len
= 0, fit
= 0;
3850 int fitlimit
= 0, eightbit
, maxfit
;
3855 * Add up the length. First, start with the parameter name.
3858 len
= strlen(pm
->pm_name
);
3861 * Scan the parameter value and see if we need to do encoding for this
3865 eightbit
= contains8bit(start
, NULL
);
3868 * Determine if we need to encode this section. Encoding is necessary if:
3870 * - There are any 8-bit characters at all and we're on the first
3872 * - There are 8-bit characters within N bytes of our section start.
3873 * N is calculated based on the number of bytes it would take to
3874 * reach CPERLIN. Specifically:
3875 * 8 (starting tab) +
3876 * strlen(param name) +
3877 * 4 ('* for section marker, '=', opening/closing '"')
3879 * is the number of bytes used by everything that isn't part of the
3880 * value. So that gets subtracted from CPERLIN.
3883 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3884 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3885 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3889 len
++; /* Add in equal sign */
3893 * We're using maxfit as a marker for how many characters we can
3894 * fit into the line. Bump it by two because we're not using quotes
3901 * If we don't have a charset or language tag in this parameter,
3905 if (! pm
->pm_charset
) {
3906 pm
->pm_charset
= getcpy(write_charset_8bit());
3907 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3908 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3909 "local character set is US-ASCII", pm
->pm_name
);
3912 pm
->pm_lang
= getcpy(NULL
); /* Default to a blank lang tag */
3914 len
++; /* For the encoding marker */
3917 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3922 * We know we definitely need to include an index. maxfit already
3923 * includes the section marker.
3925 len
+= strlen(indexchar
);
3927 for (p
= start
; *p
!= '\0'; p
++) {
3928 if (isparamencode(*p
)) {
3936 * Just so there's no confusion: maxfit is counting OUTPUT
3937 * characters (post-encoding). fit is counting INPUT characters.
3939 if (! fitlimit
&& maxfit
>= 0)
3941 else if (! fitlimit
)
3946 * Calculate the string length, but add room for quoting \
3947 * and " if necessary. Also account for quotes at beginning
3950 for (p
= start
; *p
!= '\0'; p
++) {
3961 if (! fitlimit
&& maxfit
>= 0)
3963 else if (! fitlimit
)
3980 * Output an encoded parameter string.
3984 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3985 size_t valueoff
, int index
)
3987 size_t outlen
= 0, n
;
3988 char *endptr
= output
+ len
, *p
;
3991 * First, output the marker for an encoded string.
3999 * If the index is 0, output the character set and language tag.
4000 * If theses were NULL, they should have already been filled in
4005 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
4009 if (output
> endptr
) {
4010 advise(NULL
, "Internal error: parameter buffer overflow");
4016 * Copy over the value, encoding if necessary
4019 p
= pm
->pm_value
+ valueoff
;
4020 while (valuelen
-- > 0) {
4021 if (isparamencode(*p
)) {
4022 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4029 if (output
> endptr
) {
4030 advise(NULL
, "Internal error: parameter buffer overflow");
4041 * Output a "normal" parameter, without encoding. Be sure to escape
4042 * quotes and backslashes if necessary.
4046 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4050 char *endptr
= output
+ len
, *p
;
4056 p
= pm
->pm_value
+ valueoff
;
4058 while (valuelen
-- > 0) {
4068 if (output
> endptr
) {
4069 advise(NULL
, "Internal error: parameter buffer overflow");
4074 if (output
- 2 > endptr
) {
4075 advise(NULL
, "Internal error: parameter buffer overflow");
4086 * Add a parameter to the parameter linked list
4090 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4092 PM pm
= mh_xmalloc(sizeof(*pm
));
4094 memset(pm
, 0, sizeof(*pm
));
4096 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4097 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4100 (*last
)->pm_next
= pm
;
4111 * Either replace a current parameter with a new value, or add the parameter
4112 * to the parameter linked list.
4116 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4120 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4121 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4123 * If nocopy is set, it's assumed that we own both name
4124 * and value. We don't need name, so we discard it now.
4129 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4134 return add_param(first
, last
, name
, value
, nocopy
);
4138 * Retrieve a parameter value from a parameter linked list. If the parameter
4139 * value needs converted to the local character set, do that now.
4143 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4145 while (first
!= NULL
) {
4146 if (strcasecmp(name
, first
->pm_name
) == 0) {
4148 return first
->pm_value
;
4150 return getcpy(get_param_value(first
, replace
));
4152 first
= first
->pm_next
;
4159 * Return a parameter value, converting to the local character set if
4163 char *get_param_value(PM pm
, char replace
)
4165 static char buffer
[4096]; /* I hope no parameters are larger */
4166 size_t bufsize
= sizeof(buffer
);
4171 ICONV_CONST
char *p
;
4172 #else /* HAVE_ICONV */
4174 #endif /* HAVE_ICONV */
4179 * If we don't have a character set indicated, it's assumed to be
4180 * US-ASCII. If it matches our character set, we don't need to convert
4184 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4185 strlen(pm
->pm_charset
))) {
4186 return pm
->pm_value
;
4190 * In this case, we need to convert. If we have iconv support, use
4191 * that. Otherwise, go through and simply replace every non-ASCII
4192 * character with the substitution character.
4197 bufsize
= sizeof(buffer
);
4198 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4200 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4201 if (cd
== (iconv_t
) -1) {
4205 inbytes
= strlen(pm
->pm_value
);
4209 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4210 if (errno
!= EILSEQ
) {
4215 * Reset shift state, substitute our character,
4216 * try to restart conversion.
4219 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4232 for (++p
, --inbytes
;
4233 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4252 #endif /* HAVE_ICONV */
4255 * Take everything non-ASCII and substituite the replacement character
4259 bufsize
= sizeof(buffer
);
4260 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4261 if (isascii((unsigned char) *p
) && !iscntrl((unsigned char) *p
))