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 unsigned 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 unsigned char *decoded_p
= decoded
;
1844 for (i
= 0; i
< decoded_len
; ++i
) {
1845 putc (*decoded_p
++, ce
->ce_fp
);
1847 free ((char *) decoded
);
1848 if (ferror (ce
->ce_fp
)) {
1849 content_error (ce
->ce_file
, ct
, "error writing to");
1853 if (ct
->c_digested
) {
1854 if (memcmp(digest
, ct
->c_digest
,
1855 sizeof(digest
) / sizeof(digest
[0]))) {
1856 content_error (NULL
, ct
,
1857 "content integrity suspect (digest mismatch) -- continuing");
1860 fprintf (stderr
, "content integrity confirmed\n");
1868 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1870 if (fflush (ce
->ce_fp
)) {
1871 content_error (ce
->ce_file
, ct
, "error writing to");
1875 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1878 *file
= ce
->ce_file
;
1884 return fileno (ce
->ce_fp
);
1891 free_encoding (ct
, 0);
1901 static char hex2nib
[0x80] = {
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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1909 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1910 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1911 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1913 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1914 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1915 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1916 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1917 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1924 return init_encoding (ct
, openQuoted
);
1929 openQuoted (CT ct
, char **file
)
1931 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1937 CE ce
= &ct
->c_cefile
;
1938 /* sbeck -- handle suffixes */
1943 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1948 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1949 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1955 if (*file
== NULL
) {
1958 ce
->ce_file
= add (*file
, NULL
);
1962 /* sbeck@cise.ufl.edu -- handle suffixes */
1964 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1965 if (ce
->ce_unlink
) {
1966 /* Create temporary file with filename extension. */
1967 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1968 adios(NULL
, "unable to create temporary file in %s",
1972 ce
->ce_file
= add (cp
, ce
->ce_file
);
1974 } else if (*file
== NULL
) {
1976 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1977 adios(NULL
, "unable to create temporary file in %s",
1980 ce
->ce_file
= add (tempfile
, NULL
);
1983 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1984 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1988 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1989 adios (NULL
, "internal error(2)");
1992 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1993 content_error (ct
->c_file
, ct
, "unable to open for reading");
1999 if ((digested
= ct
->c_digested
))
2000 MD5Init (&mdContext
);
2007 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2009 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2010 content_error (NULL
, ct
, "premature eof");
2014 if ((cc
= gotlen
) > len
)
2018 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2019 if (!isspace ((unsigned char) *ep
))
2023 for (; cp
< ep
; cp
++) {
2025 /* in an escape sequence */
2027 /* at byte 1 of an escape sequence */
2028 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2029 /* next is byte 2 */
2032 /* at byte 2 of an escape sequence */
2034 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2035 putc (mask
, ce
->ce_fp
);
2037 MD5Update (&mdContext
, &mask
, 1);
2038 if (ferror (ce
->ce_fp
)) {
2039 content_error (ce
->ce_file
, ct
, "error writing to");
2042 /* finished escape sequence; next may be literal or a new
2043 * escape sequence */
2046 /* on to next byte */
2050 /* not in an escape sequence */
2052 /* starting an escape sequence, or invalid '='? */
2053 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2054 /* "=\n" soft line break, eat the \n */
2058 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2059 /* We don't have 2 bytes left, so this is an invalid
2060 * escape sequence; just show the raw bytes (below). */
2061 } else if (isxdigit ((unsigned char) cp
[1]) &&
2062 isxdigit ((unsigned char) cp
[2])) {
2063 /* Next 2 bytes are hex digits, making this a valid escape
2064 * sequence; let's decode it (above). */
2068 /* One or both of the next 2 is out of range, making this
2069 * an invalid escape sequence; just show the raw bytes
2074 /* Just show the raw byte. */
2075 putc (*cp
, ce
->ce_fp
);
2078 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2080 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2083 if (ferror (ce
->ce_fp
)) {
2084 content_error (ce
->ce_file
, ct
, "error writing to");
2090 content_error (NULL
, ct
,
2091 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2095 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2097 if (fflush (ce
->ce_fp
)) {
2098 content_error (ce
->ce_file
, ct
, "error writing to");
2103 unsigned char digest
[16];
2105 MD5Final (digest
, &mdContext
);
2106 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2107 sizeof(digest
) / sizeof(digest
[0])))
2108 content_error (NULL
, ct
,
2109 "content integrity suspect (digest mismatch) -- continuing");
2112 fprintf (stderr
, "content integrity confirmed\n");
2115 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2118 *file
= ce
->ce_file
;
2124 return fileno (ce
->ce_fp
);
2127 free_encoding (ct
, 0);
2144 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2147 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2153 open7Bit (CT ct
, char **file
)
2155 int cc
, fd
, len
, own_ct_fp
= 0;
2156 char buffer
[BUFSIZ
];
2157 /* sbeck -- handle suffixes */
2160 CE ce
= &ct
->c_cefile
;
2163 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2168 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2169 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2175 if (*file
== NULL
) {
2178 ce
->ce_file
= add (*file
, NULL
);
2182 /* sbeck@cise.ufl.edu -- handle suffixes */
2184 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2185 if (ce
->ce_unlink
) {
2186 /* Create temporary file with filename extension. */
2187 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2188 adios(NULL
, "unable to create temporary file in %s",
2192 ce
->ce_file
= add (cp
, ce
->ce_file
);
2194 } else if (*file
== NULL
) {
2196 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2197 adios(NULL
, "unable to create temporary file in %s",
2200 ce
->ce_file
= add (tempfile
, NULL
);
2203 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2204 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2208 if (ct
->c_type
== CT_MULTIPART
) {
2209 CI ci
= &ct
->c_ctinfo
;
2213 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2214 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2215 + 1 + strlen (ci
->ci_subtype
);
2216 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2219 fputs (buffer
, ce
->ce_fp
);
2223 if (ci
->ci_comment
) {
2224 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2225 fputs ("\n\t", ce
->ce_fp
);
2229 putc (' ', ce
->ce_fp
);
2232 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2235 fprintf (ce
->ce_fp
, "\n");
2237 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2239 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2241 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2242 fprintf (ce
->ce_fp
, "\n");
2245 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2246 adios (NULL
, "internal error(3)");
2249 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2250 content_error (ct
->c_file
, ct
, "unable to open for reading");
2256 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2258 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2260 content_error (ct
->c_file
, ct
, "error reading from");
2264 content_error (NULL
, ct
, "premature eof");
2272 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2273 advise ("open7Bit", "fwrite");
2275 if (ferror (ce
->ce_fp
)) {
2276 content_error (ce
->ce_file
, ct
, "error writing to");
2281 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2283 if (fflush (ce
->ce_fp
)) {
2284 content_error (ce
->ce_file
, ct
, "error writing to");
2288 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2291 *file
= ce
->ce_file
;
2296 return fileno (ce
->ce_fp
);
2299 free_encoding (ct
, 0);
2313 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2315 char cachefile
[BUFSIZ
];
2318 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2323 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2324 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2330 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2331 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2332 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2333 ce
->ce_file
= getcpy (cachefile
);
2337 admonish (cachefile
, "unable to fopen for reading");
2341 *fd
= fileno (ce
->ce_fp
);
2345 *file
= ce
->ce_file
;
2346 *fd
= fileno (ce
->ce_fp
);
2357 return init_encoding (ct
, openFile
);
2362 openFile (CT ct
, char **file
)
2365 char cachefile
[BUFSIZ
];
2366 struct exbody
*e
= ct
->c_ctexbody
;
2367 CE ce
= &ct
->c_cefile
;
2369 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2381 content_error (NULL
, ct
, "missing name parameter");
2385 ce
->ce_file
= getcpy (e
->eb_name
);
2388 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2389 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2393 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2394 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2395 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2399 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2400 if ((fp
= fopen (cachefile
, "w"))) {
2402 char buffer
[BUFSIZ
];
2403 FILE *gp
= ce
->ce_fp
;
2405 fseek (gp
, 0L, SEEK_SET
);
2407 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2409 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2410 advise ("openFile", "fwrite");
2415 admonish (ce
->ce_file
, "error reading");
2416 (void) m_unlink (cachefile
);
2420 admonish (cachefile
, "error writing");
2421 (void) m_unlink (cachefile
);
2428 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2429 *file
= ce
->ce_file
;
2430 return fileno (ce
->ce_fp
);
2440 return init_encoding (ct
, openFTP
);
2445 openFTP (CT ct
, char **file
)
2447 int cachetype
, caching
, fd
;
2449 char *bp
, *ftp
, *user
, *pass
;
2450 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2452 CE ce
= &ct
->c_cefile
;
2453 static char *username
= NULL
;
2454 static char *password
= NULL
;
2458 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2464 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2475 if (!e
->eb_name
|| !e
->eb_site
) {
2476 content_error (NULL
, ct
, "missing %s parameter",
2477 e
->eb_name
? "site": "name");
2481 /* Get the buffer ready to go */
2483 buflen
= sizeof(buffer
);
2486 * Construct the query message for user
2488 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2494 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2500 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2501 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2506 if (e
->eb_size
> 0) {
2507 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2512 snprintf (bp
, buflen
, "? ");
2515 * Now, check the answer
2517 if (!read_yes_or_no_if_tty (buffer
))
2522 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2526 ruserpass (e
->eb_site
, &username
, &password
);
2531 ce
->ce_unlink
= (*file
== NULL
);
2533 cachefile
[0] = '\0';
2534 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2535 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2536 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2537 if (*file
== NULL
) {
2544 ce
->ce_file
= add (*file
, NULL
);
2546 ce
->ce_file
= add (cachefile
, NULL
);
2549 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2550 adios(NULL
, "unable to create temporary file in %s",
2553 ce
->ce_file
= add (tempfile
, NULL
);
2556 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2557 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2562 int child_id
, i
, vecp
;
2566 vec
[vecp
++] = r1bindex (ftp
, '/');
2567 vec
[vecp
++] = e
->eb_site
;
2570 vec
[vecp
++] = e
->eb_dir
;
2571 vec
[vecp
++] = e
->eb_name
;
2572 vec
[vecp
++] = ce
->ce_file
,
2573 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2574 ? "ascii" : "binary";
2579 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2583 adios ("fork", "unable to");
2587 close (fileno (ce
->ce_fp
));
2589 fprintf (stderr
, "unable to exec ");
2595 if (pidXwait (child_id
, NULL
)) {
2596 username
= password
= NULL
;
2606 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2611 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2612 if ((fp
= fopen (cachefile
, "w"))) {
2614 FILE *gp
= ce
->ce_fp
;
2616 fseek (gp
, 0L, SEEK_SET
);
2618 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2620 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2621 advise ("openFTP", "fwrite");
2626 admonish (ce
->ce_file
, "error reading");
2627 (void) m_unlink (cachefile
);
2631 admonish (cachefile
, "error writing");
2632 (void) m_unlink (cachefile
);
2640 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2641 *file
= ce
->ce_file
;
2642 return fileno (ce
->ce_fp
);
2653 return init_encoding (ct
, openMail
);
2658 openMail (CT ct
, char **file
)
2660 int child_id
, fd
, i
, vecp
;
2662 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2663 struct exbody
*e
= ct
->c_ctexbody
;
2664 CE ce
= &ct
->c_cefile
;
2666 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2677 if (!e
->eb_server
) {
2678 content_error (NULL
, ct
, "missing server parameter");
2682 /* Get buffer ready to go */
2684 buflen
= sizeof(buffer
);
2686 /* Now, construct query message */
2687 snprintf (bp
, buflen
, "Retrieve content");
2693 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2699 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2701 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2703 /* Now, check answer */
2704 if (!read_yes_or_no_if_tty (buffer
))
2708 vec
[vecp
++] = r1bindex (mailproc
, '/');
2709 vec
[vecp
++] = e
->eb_server
;
2710 vec
[vecp
++] = "-subject";
2711 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2712 vec
[vecp
++] = "-body";
2713 vec
[vecp
++] = e
->eb_body
;
2716 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2720 advise ("fork", "unable to");
2724 execvp (mailproc
, vec
);
2725 fprintf (stderr
, "unable to exec ");
2731 if (pidXwait (child_id
, NULL
) == OK
)
2732 advise (NULL
, "request sent");
2736 if (*file
== NULL
) {
2738 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2739 adios(NULL
, "unable to create temporary file in %s",
2742 ce
->ce_file
= add (tempfile
, NULL
);
2745 ce
->ce_file
= add (*file
, NULL
);
2749 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2750 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2754 /* showproc is for mhshow and mhstore, though mhlist -debug
2755 * prints it, too. */
2757 free (ct
->c_showproc
);
2758 ct
->c_showproc
= add ("true", NULL
);
2760 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2761 *file
= ce
->ce_file
;
2762 return fileno (ce
->ce_fp
);
2773 return init_encoding (ct
, openURL
);
2778 openURL (CT ct
, char **file
)
2780 struct exbody
*e
= ct
->c_ctexbody
;
2781 CE ce
= &ct
->c_cefile
;
2782 char *urlprog
, *program
;
2783 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2784 int fd
, caching
, cachetype
;
2785 struct msgs_array args
= { 0, 0, NULL
};
2788 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2792 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2796 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2808 content_error(NULL
, ct
, "missing url parameter");
2812 ce
->ce_unlink
= (*file
== NULL
);
2814 cachefile
[0] = '\0';
2816 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2817 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2818 if (*file
== NULL
) {
2825 ce
->ce_file
= add(*file
, NULL
);
2827 ce
->ce_file
= add(cachefile
, NULL
);
2830 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2831 adios(NULL
, "unable to create temporary file in %s",
2834 ce
->ce_file
= add (tempfile
, NULL
);
2837 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2838 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2842 switch (child_id
= fork()) {
2844 adios ("fork", "unable to");
2848 argsplit_msgarg(&args
, urlprog
, &program
);
2849 app_msgarg(&args
, e
->eb_url
);
2850 app_msgarg(&args
, NULL
);
2851 dup2(fileno(ce
->ce_fp
), 1);
2852 close(fileno(ce
->ce_fp
));
2853 execvp(program
, args
.msgs
);
2854 fprintf(stderr
, "Unable to exec ");
2860 if (pidXwait(child_id
, NULL
)) {
2868 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2873 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2874 if ((fp
= fopen(cachefile
, "w"))) {
2876 FILE *gp
= ce
->ce_fp
;
2878 fseeko(gp
, 0, SEEK_SET
);
2880 while ((cc
= fread(buffer
, sizeof(*buffer
),
2881 sizeof(buffer
), gp
)) > 0)
2882 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2883 advise ("openURL", "fwrite");
2889 admonish(ce
->ce_file
, "error reading");
2890 (void) m_unlink (cachefile
);
2897 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2898 *file
= ce
->ce_file
;
2904 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2905 * has to be base64 decoded.
2908 readDigest (CT ct
, char *cp
)
2910 unsigned char *digest
;
2913 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2914 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2916 if (strlen ((char *) digest
) <= maxlen
) {
2917 memcpy (ct
->c_digest
, digest
, maxlen
);
2922 fprintf (stderr
, "MD5 digest=");
2923 for (i
= 0; i
< maxlen
; ++i
) {
2924 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2926 fprintf (stderr
, "\n");
2932 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2933 (int) strlen ((char *) digest
));
2944 /* Multipart parts might have content before the first subpart and/or
2945 after the last subpart that hasn't been stored anywhere else, so do
2948 get_leftover_mp_content (CT ct
, int before
/* or after */)
2950 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2952 int found_boundary
= 0;
2958 char *content
= NULL
;
2960 if (! m
) return NOTOK
;
2963 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2965 /* Isolate the beginning of this part to the beginning of the
2966 first subpart and save any content between them. */
2967 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2968 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2969 boundary
= concat ("--", m
->mp_start
, NULL
);
2971 struct part
*last_subpart
= NULL
;
2972 struct part
*subpart
;
2974 /* Go to the last subpart to get its end position. */
2975 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2976 last_subpart
= subpart
;
2979 if (last_subpart
== NULL
) return NOTOK
;
2981 /* Isolate the end of the last subpart to the end of this part
2982 and save any content between them. */
2983 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2984 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2985 boundary
= concat ("--", m
->mp_stop
, NULL
);
2988 /* Back up by 1 to pick up the newline. */
2989 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2991 /* Don't look beyond beginning of first subpart (before) or
2992 next part (after). */
2993 if (read
> max
) bufp
[read
-max
] = '\0';
2996 if (! strcmp (bufp
, boundary
)) {
3000 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3006 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3008 char *old_content
= content
;
3009 content
= concat (content
, bufp
, NULL
);
3013 ? concat ("\n", bufp
, NULL
)
3014 : concat (bufp
, NULL
);
3019 if (found_boundary
|| read
> max
) break;
3021 if (read
> max
) break;
3025 /* Skip the newline if that's all there is. */
3029 /* Remove trailing newline, except at EOF. */
3030 if ((before
|| ! feof (ct
->c_fp
)) &&
3031 (cp
= content
+ strlen (content
)) > content
&&
3036 if (strlen (content
) > 1) {
3038 m
->mp_content_before
= content
;
3040 m
->mp_content_after
= content
;
3055 ct_type_str (int type
) {
3057 case CT_APPLICATION
:
3058 return "application";
3074 return "unknown_type";
3080 ct_subtype_str (int type
, int subtype
) {
3082 case CT_APPLICATION
:
3084 case APPLICATION_OCTETS
:
3086 case APPLICATION_POSTSCRIPT
:
3087 return "postscript";
3089 return "unknown_app_subtype";
3093 case MESSAGE_RFC822
:
3095 case MESSAGE_PARTIAL
:
3097 case MESSAGE_EXTERNAL
:
3100 return "unknown_msg_subtype";
3106 case MULTI_ALTERNATE
:
3107 return "alternative";
3110 case MULTI_PARALLEL
:
3115 return "unknown_multipart_subtype";
3126 return "unknown_text_subtype";
3129 return "unknown_type";
3135 ct_str_type (const char *type
) {
3136 struct str2init
*s2i
;
3138 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3139 if (! strcasecmp (type
, s2i
->si_key
)) {
3143 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3152 ct_str_subtype (int type
, const char *subtype
) {
3156 case CT_APPLICATION
:
3157 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3158 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3162 return kv
->kv_value
;
3164 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3165 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3169 return kv
->kv_value
;
3171 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3172 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3176 return kv
->kv_value
;
3178 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3179 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3183 return kv
->kv_value
;
3190 /* Find the content type and InitFunc for the CT. */
3191 const struct str2init
*
3192 get_ct_init (int type
) {
3193 const struct str2init
*sp
;
3195 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3196 if (type
== sp
->si_val
) {
3205 ce_str (int encoding
) {
3210 return "quoted-printable";
3226 /* Find the content type and InitFunc for the content encoding method. */
3227 const struct str2init
*
3228 get_ce_method (const char *method
) {
3229 struct str2init
*sp
;
3231 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3232 if (! strcasecmp (method
, sp
->si_key
)) {
3241 * Parse a series of MIME attributes (or parameters) given a header as
3244 * Arguments include:
3246 * filename - Name of input file (for error messages)
3247 * fieldname - Name of field being processed
3248 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3249 * Updated to point to end of attributes when finished.
3250 * param_head - Pointer to head of parameter list
3251 * param_tail - Pointer to tail of parameter list
3252 * commentp - Pointer to header comment pointer (may be NULL)
3254 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3255 * DONE to indicate a benign error (minor parsing error, but the program
3260 parse_header_attrs (const char *filename
, const char *fieldname
,
3261 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3264 char *cp
= *header_attrp
;
3270 struct sectlist
*next
;
3276 struct sectlist
*sechead
;
3277 struct parmlist
*next
;
3278 } *pp
, *pp2
, *phead
= NULL
;
3280 while (*cp
== ';') {
3281 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3282 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3285 while (isspace ((unsigned char) *cp
))
3289 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3294 if (! suppress_extraneous_trailing_semicolon_warning
) {
3296 "extraneous trailing ';' in message %s's %s: "
3298 filename
, fieldname
);
3300 extraneous_trailing_semicolon
= 1;
3304 /* down case the attribute name */
3305 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3306 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
3307 *dp
= tolower ((unsigned char) *dp
);
3309 for (up
= dp
; isspace ((unsigned char) *dp
);)
3311 if (dp
== cp
|| *dp
!= '=') {
3313 "invalid parameter in message %s's %s: "
3314 "field\n%*sparameter %s (error detected at offset %d)",
3315 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3320 * To handle RFC 2231, we have to deal with the following extensions:
3322 * name*=encoded-value
3323 * name*<N>=part-N-of-a-parameter-value
3324 * name*<N>*=encoded-part-N-of-a-parameter-value
3327 * If there's a * right before the equal sign, it's encoded.
3328 * If there's a * and one or more digits, then it's section N.
3330 * Remember we can have one or the other, or both. cp points to
3331 * beginning of name, up points past the last character in the
3335 for (vp
= cp
; vp
< up
; vp
++) {
3336 if (*vp
== '*' && vp
< up
- 1) {
3339 } else if (*vp
== '*' && vp
== up
- 1) {
3341 } else if (partial
) {
3342 if (isdigit((unsigned char) *vp
))
3343 index
= *vp
- '0' + index
* 10;
3345 advise (NULL
, "invalid parameter index in message %s's "
3346 "%s: field\n%*s(parameter %s)", filename
,
3347 fieldname
, strlen(invo_name
) + 2, "", cp
);
3356 * Break out the parameter name and value sections and allocate
3360 nameptr
= mh_xmalloc(len
+ 1);
3361 strncpy(nameptr
, cp
, len
);
3362 nameptr
[len
] = '\0';
3364 for (dp
++; isspace ((unsigned char) *dp
);)
3369 * Single quotes delimit the character set and language tag.
3370 * They are required on the first section (or a complete
3375 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3381 charset
= mh_xmalloc(len
+ 1);
3382 strncpy(charset
, dp
, len
);
3383 charset
[len
] = '\0';
3389 advise(NULL
, "missing charset in message %s's %s: "
3390 "field\n%*s(parameter %s)", filename
, fieldname
,
3391 strlen(invo_name
) + 2, "", nameptr
);
3397 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3404 lang
= mh_xmalloc(len
+ 1);
3405 strncpy(lang
, dp
, len
);
3412 advise(NULL
, "missing language tag in message %s's %s: "
3413 "field\n%*s(parameter %s)", filename
, fieldname
,
3414 strlen(invo_name
) + 2, "", nameptr
);
3425 * At this point vp should be pointing at the beginning
3426 * of the encoded value/section. Continue until we reach
3427 * the end or get whitespace. But first, calculate the
3428 * length so we can allocate the correct buffer size.
3431 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3433 if (*(vp
+ 1) == '\0' ||
3434 !isxdigit((unsigned char) *(vp
+ 1)) ||
3435 *(vp
+ 2) == '\0' ||
3436 !isxdigit((unsigned char) *(vp
+ 2))) {
3437 advise(NULL
, "invalid encoded sequence in message "
3438 "%s's %s: field\n%*s(parameter %s)",
3439 filename
, fieldname
, strlen(invo_name
) + 2,
3453 up
= valptr
= mh_xmalloc(len
+ 1);
3455 for (vp
= dp
; istoken(*vp
); vp
++) {
3457 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3468 * A "normal" string. If it's got a leading quote, then we
3469 * strip the quotes out. Otherwise go until we reach the end
3470 * or get whitespace. Note we scan it twice; once to get the
3471 * length, then the second time copies it into the destination
3478 for (cp
= dp
+ 1;;) {
3483 "invalid quoted-string in message %s's %s: "
3484 "field\n%*s(parameter %s)",
3485 filename
, fieldname
, strlen(invo_name
) + 2, "",
3508 for (cp
= dp
; istoken (*cp
); cp
++) {
3513 valptr
= mh_xmalloc(len
+ 1);
3517 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3525 strncpy(valptr
, cp
= dp
, len
);
3533 * If 'partial' is set, we don't allocate a parameter now. We
3534 * put it on the parameter linked list to be reassembled later.
3536 * "phead" points to a list of all parameters we need to reassemble.
3537 * Each parameter has a list of sections. We insert the sections in
3542 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3543 if (strcasecmp(nameptr
, pp
->name
) == 0)
3548 pp
= mh_xmalloc(sizeof(*pp
));
3549 memset(pp
, 0, sizeof(*pp
));
3556 * Insert this into the section linked list
3559 sp
= mh_xmalloc(sizeof(*sp
));
3560 memset(sp
, 0, sizeof(*sp
));
3565 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3566 sp
->next
= pp
->sechead
;
3569 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3570 if (sp2
->index
== sp
->index
) {
3571 advise (NULL
, "duplicate index (%d) in message "
3572 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3573 filename
, fieldname
, strlen(invo_name
) + 2, "",
3578 if (sp2
->index
< sp
->index
&&
3579 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3580 sp
->next
= sp2
->next
;
3587 advise(NULL
, "Internal error: cannot insert partial "
3588 "param in message %s's %s: field\n%*s(parameter %s)",
3589 filename
, fieldname
, strlen(invo_name
) + 2, "",
3597 * Save our charset and lang tags.
3600 if (index
== 0 && encoded
) {
3603 pp
->charset
= charset
;
3609 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3610 pm
->pm_charset
= charset
;
3614 while (isspace ((unsigned char) *cp
))
3618 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3624 * Now that we're done, reassemble all of the partial parameters.
3627 for (pp
= phead
; pp
!= NULL
; ) {
3631 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3632 if (sp
->index
!= pindex
++) {
3633 advise(NULL
, "missing section %d for parameter in "
3634 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3635 filename
, fieldname
, strlen(invo_name
) + 2, "",
3642 p
= q
= mh_xmalloc(tlen
+ 1);
3643 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3644 memcpy(q
, sp
->value
, sp
->len
);
3654 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3655 pm
->pm_charset
= pp
->charset
;
3656 pm
->pm_lang
= pp
->lang
;
3667 * Return the charset for a particular content type.
3671 content_charset (CT ct
) {
3672 char *ret_charset
= NULL
;
3674 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3676 return ret_charset
? ret_charset
: getcpy ("US-ASCII");
3681 * Create a string based on a list of output parameters. Assume that this
3682 * parameter string will be appended to an existing header, so start out
3683 * with the separator (;). Perform RFC 2231 encoding when necessary.
3687 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3689 char *paramout
= NULL
;
3690 char line
[CPERLIN
* 2], *q
;
3691 int curlen
, index
, cont
, encode
, i
;
3692 size_t valoff
, numchars
;
3694 while (params
!= NULL
) {
3700 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3703 if (strlen(params
->pm_name
) > CPERLIN
) {
3704 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3710 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3713 * Loop until we get a parameter that fits within a line. We
3714 * assume new lines start with a tab, so check our overflow based
3724 * At this point we're definitely continuing the line, so
3725 * be sure to include the parameter name and section index.
3728 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3729 params
->pm_name
, index
);
3732 * Both of these functions do a NUL termination
3736 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3737 numchars
, valoff
, index
);
3739 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3750 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3755 * "line" starts with a ;\n\t, so that doesn't count against
3756 * the length. But add 8 since it starts with a tab; that's
3757 * how we end up with 5.
3760 initialwidth
= strlen(line
) + 5;
3763 * At this point the line should be built, so add it to our
3764 * current output buffer.
3767 paramout
= add(line
, paramout
);
3771 * If this won't fit on the line, start a new one. Save room in
3772 * case we need a semicolon on the end
3775 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3787 * At this point, we're either finishing a contined parameter, or
3788 * we're working on a new one.
3792 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3793 params
->pm_name
, index
);
3795 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3800 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3801 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3803 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3804 strlen(params
->pm_value
+ valoff
), valoff
);
3812 paramout
= add(line
, paramout
);
3813 initialwidth
+= strlen(line
);
3815 params
= params
->pm_next
;
3819 *offsetout
= initialwidth
;
3825 * Calculate the size of a parameter.
3829 * pm - The parameter being output
3830 * index - If continuing the parameter, the index of the section
3832 * valueoff - The current offset into the parameter value that we're
3833 * working on (previous sections have consumed valueoff bytes).
3834 * encode - Set if we should perform encoding on this parameter section
3835 * (given that we're consuming bytesfit bytes).
3836 * cont - Set if the remaining data in value will not fit on a single
3837 * line and will need to be continued.
3838 * bytesfit - The number of bytes that we can consume from the parameter
3839 * value and still fit on a completely new line. The
3840 * calculation assumes the new line starts with a tab,
3841 * includes the parameter name and any encoding, and fits
3842 * within CPERLIN bytes. Will always be at least 1.
3846 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3849 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3850 size_t len
= 0, fit
= 0;
3851 int fitlimit
= 0, eightbit
, maxfit
;
3856 * Add up the length. First, start with the parameter name.
3859 len
= strlen(pm
->pm_name
);
3862 * Scan the parameter value and see if we need to do encoding for this
3866 eightbit
= contains8bit(start
, NULL
);
3869 * Determine if we need to encode this section. Encoding is necessary if:
3871 * - There are any 8-bit characters at all and we're on the first
3873 * - There are 8-bit characters within N bytes of our section start.
3874 * N is calculated based on the number of bytes it would take to
3875 * reach CPERLIN. Specifically:
3876 * 8 (starting tab) +
3877 * strlen(param name) +
3878 * 4 ('* for section marker, '=', opening/closing '"')
3880 * is the number of bytes used by everything that isn't part of the
3881 * value. So that gets subtracted from CPERLIN.
3884 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3885 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3886 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3890 len
++; /* Add in equal sign */
3894 * We're using maxfit as a marker for how many characters we can
3895 * fit into the line. Bump it by two because we're not using quotes
3902 * If we don't have a charset or language tag in this parameter,
3906 if (! pm
->pm_charset
) {
3907 pm
->pm_charset
= getcpy(write_charset_8bit());
3908 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3909 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3910 "local character set is US-ASCII", pm
->pm_name
);
3913 pm
->pm_lang
= getcpy(NULL
); /* Default to a blank lang tag */
3915 len
++; /* For the encoding marker */
3918 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3923 * We know we definitely need to include an index. maxfit already
3924 * includes the section marker.
3926 len
+= strlen(indexchar
);
3928 for (p
= start
; *p
!= '\0'; p
++) {
3929 if (isparamencode(*p
)) {
3937 * Just so there's no confusion: maxfit is counting OUTPUT
3938 * characters (post-encoding). fit is counting INPUT characters.
3940 if (! fitlimit
&& maxfit
>= 0)
3942 else if (! fitlimit
)
3947 * Calculate the string length, but add room for quoting \
3948 * and " if necessary. Also account for quotes at beginning
3951 for (p
= start
; *p
!= '\0'; p
++) {
3962 if (! fitlimit
&& maxfit
>= 0)
3964 else if (! fitlimit
)
3981 * Output an encoded parameter string.
3985 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3986 size_t valueoff
, int index
)
3988 size_t outlen
= 0, n
;
3989 char *endptr
= output
+ len
, *p
;
3992 * First, output the marker for an encoded string.
4000 * If the index is 0, output the character set and language tag.
4001 * If theses were NULL, they should have already been filled in
4006 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
4010 if (output
> endptr
) {
4011 advise(NULL
, "Internal error: parameter buffer overflow");
4017 * Copy over the value, encoding if necessary
4020 p
= pm
->pm_value
+ valueoff
;
4021 while (valuelen
-- > 0) {
4022 if (isparamencode(*p
)) {
4023 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4030 if (output
> endptr
) {
4031 advise(NULL
, "Internal error: parameter buffer overflow");
4042 * Output a "normal" parameter, without encoding. Be sure to escape
4043 * quotes and backslashes if necessary.
4047 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4051 char *endptr
= output
+ len
, *p
;
4057 p
= pm
->pm_value
+ valueoff
;
4059 while (valuelen
-- > 0) {
4069 if (output
> endptr
) {
4070 advise(NULL
, "Internal error: parameter buffer overflow");
4075 if (output
- 2 > endptr
) {
4076 advise(NULL
, "Internal error: parameter buffer overflow");
4087 * Add a parameter to the parameter linked list
4091 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4093 PM pm
= mh_xmalloc(sizeof(*pm
));
4095 memset(pm
, 0, sizeof(*pm
));
4097 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4098 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4101 (*last
)->pm_next
= pm
;
4112 * Either replace a current parameter with a new value, or add the parameter
4113 * to the parameter linked list.
4117 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4121 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4122 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4124 * If nocopy is set, it's assumed that we own both name
4125 * and value. We don't need name, so we discard it now.
4130 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4135 return add_param(first
, last
, name
, value
, nocopy
);
4139 * Retrieve a parameter value from a parameter linked list. If the parameter
4140 * value needs converted to the local character set, do that now.
4144 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4146 while (first
!= NULL
) {
4147 if (strcasecmp(name
, first
->pm_name
) == 0) {
4149 return first
->pm_value
;
4151 return getcpy(get_param_value(first
, replace
));
4153 first
= first
->pm_next
;
4160 * Return a parameter value, converting to the local character set if
4164 char *get_param_value(PM pm
, char replace
)
4166 static char buffer
[4096]; /* I hope no parameters are larger */
4167 size_t bufsize
= sizeof(buffer
);
4172 ICONV_CONST
char *p
;
4173 #else /* HAVE_ICONV */
4175 #endif /* HAVE_ICONV */
4180 * If we don't have a character set indicated, it's assumed to be
4181 * US-ASCII. If it matches our character set, we don't need to convert
4185 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4186 strlen(pm
->pm_charset
))) {
4187 return pm
->pm_value
;
4191 * In this case, we need to convert. If we have iconv support, use
4192 * that. Otherwise, go through and simply replace every non-ASCII
4193 * character with the substitution character.
4198 bufsize
= sizeof(buffer
);
4199 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4201 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4202 if (cd
== (iconv_t
) -1) {
4206 inbytes
= strlen(pm
->pm_value
);
4210 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4211 if (errno
!= EILSEQ
) {
4216 * Reset shift state, substitute our character,
4217 * try to restart conversion.
4220 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4233 for (++p
, --inbytes
;
4234 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4253 #endif /* HAVE_ICONV */
4256 * Take everything non-ASCII and substituite the replacement character
4260 bufsize
= sizeof(buffer
);
4261 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4262 if (isascii((unsigned char) *p
) && !iscntrl((unsigned char) *p
))