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 normal_param(PM
, char *, size_t, size_t, size_t);
155 static int get_dispo (char *, CT
, int);
157 struct str2init str2cts
[] = {
158 { "application", CT_APPLICATION
, InitApplication
},
159 { "audio", CT_AUDIO
, InitGeneric
},
160 { "image", CT_IMAGE
, InitGeneric
},
161 { "message", CT_MESSAGE
, InitMessage
},
162 { "multipart", CT_MULTIPART
, InitMultiPart
},
163 { "text", CT_TEXT
, InitText
},
164 { "video", CT_VIDEO
, InitGeneric
},
165 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
166 { NULL
, CT_UNKNOWN
, NULL
},
169 struct str2init str2ces
[] = {
170 { "base64", CE_BASE64
, InitBase64
},
171 { "quoted-printable", CE_QUOTED
, InitQuoted
},
172 { "8bit", CE_8BIT
, Init7Bit
},
173 { "7bit", CE_7BIT
, Init7Bit
},
174 { "binary", CE_BINARY
, Init7Bit
},
175 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
176 { NULL
, CE_UNKNOWN
, NULL
},
180 * NOTE WELL: si_key MUST NOT have value of NOTOK
182 * si_key is 1 if access method is anonymous.
184 struct str2init str2methods
[] = {
185 { "afs", 1, InitFile
},
186 { "anon-ftp", 1, InitFTP
},
187 { "ftp", 0, InitFTP
},
188 { "local-file", 0, InitFile
},
189 { "mail-server", 0, InitMail
},
190 { "url", 0, InitURL
},
196 * Main entry point for parsing a MIME message or file.
197 * It returns the Content structure for the top level
198 * entity in the file.
202 parse_mime (char *file
)
211 * Check if file is actually standard input
213 if ((is_stdin
= !(strcmp (file
, "-")))) {
214 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
216 advise("mhparse", "unable to create temporary file in %s",
220 file
= add (tfile
, NULL
);
222 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
223 if (fwrite(buffer
, 1, n
, fp
) != n
) {
224 (void) m_unlink (file
);
225 advise (file
, "error copying to temporary file");
231 if (ferror (stdin
)) {
232 (void) m_unlink (file
);
233 advise ("stdin", "error reading");
237 (void) m_unlink (file
);
238 advise (file
, "error writing");
241 fseek (fp
, 0L, SEEK_SET
);
242 } else if ((fp
= fopen (file
, "r")) == NULL
) {
243 advise (file
, "unable to read");
247 if (!(ct
= get_content (fp
, file
, 1))) {
249 (void) m_unlink (file
);
250 advise (NULL
, "unable to decode %s", file
);
255 ct
->c_unlink
= 1; /* temp file to remove */
259 if (ct
->c_end
== 0L) {
260 fseek (fp
, 0L, SEEK_END
);
261 ct
->c_end
= ftell (fp
);
264 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
276 * Main routine for reading/parsing the headers
277 * of a message content.
279 * toplevel = 1 # we are at the top level of the message
280 * toplevel = 0 # we are inside message type or multipart type
281 * # other than multipart/digest
282 * toplevel = -1 # we are inside multipart/digest
283 * NB: on failure we will fclose(in)!
287 get_content (FILE *in
, char *file
, int toplevel
)
290 char buf
[BUFSIZ
], name
[NAMESZ
];
294 m_getfld_state_t gstate
= 0;
296 /* allocate the content structure */
299 ct
->c_file
= add (file
, NULL
);
300 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
303 * Parse the header fields for this
304 * content into a linked list.
306 m_getfld_track_filepos (&gstate
, in
);
307 for (compnum
= 1;;) {
308 int bufsz
= sizeof buf
;
309 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
314 /* get copies of the buffers */
315 np
= add (name
, NULL
);
316 vp
= add (buf
, NULL
);
318 /* if necessary, get rest of field */
319 while (state
== FLDPLUS
) {
321 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
322 vp
= add (buf
, vp
); /* add to previous value */
325 /* Now add the header data to the list */
326 add_header (ct
, np
, vp
);
328 /* continue, to see if this isn't the last header field */
329 ct
->c_begin
= ftell (in
) + 1;
333 ct
->c_begin
= ftell (in
) - strlen (buf
);
337 ct
->c_begin
= ftell (in
);
342 adios (NULL
, "message format error in component #%d", compnum
);
345 adios (NULL
, "getfld() returned %d", state
);
348 /* break out of the loop */
351 m_getfld_state_destroy (&gstate
);
354 * Read the content headers. We will parse the
355 * MIME related header fields into their various
356 * structures and set internal flags related to
357 * content type/subtype, etc.
360 hp
= ct
->c_first_hf
; /* start at first header field */
362 /* Get MIME-Version field */
363 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
368 vrsn
= add (hp
->value
, NULL
);
370 /* Now, cleanup this field */
373 while (isspace ((unsigned char) *cp
))
375 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
377 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
378 if (!isspace ((unsigned char) *dp
))
382 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
385 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
388 for (dp
= cp
; istoken (*dp
); dp
++)
392 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
395 admonish (NULL
, "message %s has unknown value for %s: field (%s)",
396 ct
->c_file
, VRSN_FIELD
, cp
);
401 if (! suppress_multiple_mime_version_warning
)
402 advise (NULL
, "message %s has multiple %s: fields",
403 ct
->c_file
, VRSN_FIELD
);
407 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
408 /* Get Content-Type field */
409 struct str2init
*s2i
;
410 CI ci
= &ct
->c_ctinfo
;
412 /* Check if we've already seen a Content-Type header */
414 advise (NULL
, "message %s has multiple %s: fields",
415 ct
->c_file
, TYPE_FIELD
);
419 /* Parse the Content-Type field */
420 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
424 * Set the Init function and the internal
425 * flag for this content type.
427 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
428 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
430 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
432 ct
->c_type
= s2i
->si_val
;
433 ct
->c_ctinitfnx
= s2i
->si_init
;
435 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
436 /* Get Content-Transfer-Encoding field */
438 struct str2init
*s2i
;
441 * Check if we've already seen the
442 * Content-Transfer-Encoding field
445 advise (NULL
, "message %s has multiple %s: fields",
446 ct
->c_file
, ENCODING_FIELD
);
450 /* get copy of this field */
451 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
453 while (isspace ((unsigned char) *cp
))
455 for (dp
= cp
; istoken (*dp
); dp
++)
461 * Find the internal flag and Init function
462 * for this transfer encoding.
464 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
465 if (!strcasecmp (cp
, s2i
->si_key
))
467 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
470 ct
->c_encoding
= s2i
->si_val
;
472 /* Call the Init function for this encoding */
473 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
476 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
477 /* Get Content-MD5 field */
483 if (ct
->c_digested
) {
484 advise (NULL
, "message %s has multiple %s: fields",
485 ct
->c_file
, MD5_FIELD
);
489 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
491 while (isspace ((unsigned char) *cp
))
493 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
495 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
496 if (!isspace ((unsigned char) *dp
))
500 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
503 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
508 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
516 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
517 /* Get Content-ID field */
518 ct
->c_id
= add (hp
->value
, ct
->c_id
);
520 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
521 /* Get Content-Description field */
522 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
524 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
525 /* Get Content-Disposition field */
526 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
531 hp
= hp
->next
; /* next header field */
535 * Check if we saw a Content-Type field.
536 * If not, then assign a default value for
537 * it, and the Init function.
541 * If we are inside a multipart/digest message,
542 * so default type is message/rfc822
545 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
547 ct
->c_type
= CT_MESSAGE
;
548 ct
->c_ctinitfnx
= InitMessage
;
551 * Else default type is text/plain
553 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
555 ct
->c_type
= CT_TEXT
;
556 ct
->c_ctinitfnx
= InitText
;
560 /* Use default Transfer-Encoding, if necessary */
562 ct
->c_encoding
= CE_7BIT
;
575 * small routine to add header field to list
579 add_header (CT ct
, char *name
, char *value
)
583 /* allocate header field structure */
586 /* link data into header structure */
591 /* link header structure into the list */
592 if (ct
->c_first_hf
== NULL
) {
593 ct
->c_first_hf
= hp
; /* this is the first */
596 ct
->c_last_hf
->next
= hp
; /* add it to the end */
605 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
606 * directives. Fills in the information of the CTinfo structure.
609 get_ctinfo (char *cp
, CT ct
, int magic
)
618 /* store copy of Content-Type line */
619 cp
= ct
->c_ctline
= add (cp
, NULL
);
621 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
624 /* change newlines to spaces */
625 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
628 /* trim trailing spaces */
629 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
630 if (!isspace ((unsigned char) *dp
))
635 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
637 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
638 &ci
->ci_comment
) == NOTOK
)
641 for (dp
= cp
; istoken (*dp
); dp
++)
644 ci
->ci_type
= add (cp
, NULL
); /* store content type */
648 advise (NULL
, "invalid %s: field in message %s (empty type)",
649 TYPE_FIELD
, ct
->c_file
);
653 /* down case the content type string */
654 for (dp
= ci
->ci_type
; *dp
; dp
++)
655 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
656 *dp
= tolower ((unsigned char) *dp
);
658 while (isspace ((unsigned char) *cp
))
661 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
662 &ci
->ci_comment
) == NOTOK
)
667 ci
->ci_subtype
= add ("", NULL
);
672 while (isspace ((unsigned char) *cp
))
675 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
676 &ci
->ci_comment
) == NOTOK
)
679 for (dp
= cp
; istoken (*dp
); dp
++)
682 ci
->ci_subtype
= add (cp
, NULL
); /* store the content subtype */
685 if (!*ci
->ci_subtype
) {
687 "invalid %s: field in message %s (empty subtype for \"%s\")",
688 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
692 /* down case the content subtype string */
693 for (dp
= ci
->ci_subtype
; *dp
; dp
++)
694 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
695 *dp
= tolower ((unsigned char) *dp
);
698 while (isspace ((unsigned char) *cp
))
701 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
702 &ci
->ci_comment
) == NOTOK
)
705 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
706 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
707 &ci
->ci_comment
)) != OK
) {
708 return status
== NOTOK
? NOTOK
: OK
;
712 * Get any <Content-Id> given in buffer
714 if (magic
&& *cp
== '<') {
719 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
720 advise (NULL
, "invalid ID in message %s", ct
->c_file
);
726 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
732 while (isspace ((unsigned char) *cp
))
737 * Get any [Content-Description] given in buffer.
739 if (magic
&& *cp
== '[') {
741 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
745 advise (NULL
, "invalid description in message %s", ct
->c_file
);
753 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
759 while (isspace ((unsigned char) *cp
))
764 * Get any {Content-Disposition} given in buffer.
766 if (magic
&& *cp
== '{') {
768 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
772 advise (NULL
, "invalid disposition in message %s", ct
->c_file
);
780 if (get_dispo(cp
, ct
, 1) != OK
)
786 while (isspace ((unsigned char) *cp
))
791 * Get any extension directives (right now just the content transfer
792 * encoding, but maybe others) that we care about.
795 if (magic
&& *cp
== '*') {
797 * See if it's a CTE we match on
802 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
806 advise (NULL
, "invalid null transfer encoding specification");
813 ct
->c_reqencoding
= CE_UNKNOWN
;
815 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
816 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
817 ct
->c_reqencoding
= kv
->kv_value
;
822 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
823 advise (NULL
, "invalid CTE specification: \"%s\"", dp
);
827 while (isspace ((unsigned char) *cp
))
832 * Check if anything is left over
836 ci
->ci_magic
= add (cp
, NULL
);
838 /* If there is a Content-Disposition header and it doesn't
839 have a *filename=, extract it from the magic contents.
840 The r1bindex call skips any leading directory
842 if (ct
->c_dispo_type
&&
843 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
844 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
845 r1bindex(ci
->ci_magic
, '/'), 0);
850 "extraneous information in message %s's %s: field\n%*s(%s)",
851 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
859 * Parse out a Content-Disposition header. A lot of this is cribbed from
863 get_dispo (char *cp
, CT ct
, int buildflag
)
865 char *dp
, *dispoheader
;
870 * Save the whole copy of the Content-Disposition header, unless we're
871 * processing a mhbuild directive. A NULL c_dispo will be a flag to
872 * mhbuild that the disposition header needs to be generated at that
876 dispoheader
= cp
= add(cp
, NULL
);
878 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
881 /* change newlines to spaces */
882 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
885 /* trim trailing spaces */
886 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
887 if (!isspace ((unsigned char) *dp
))
892 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
894 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
900 for (dp
= cp
; istoken (*dp
); dp
++)
903 ct
->c_dispo_type
= add (cp
, NULL
); /* store disposition type */
906 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
909 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
910 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
912 if (status
== NOTOK
) {
918 "extraneous information in message %s's %s: field\n%*s(%s)",
919 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
925 ct
->c_dispo
= dispoheader
;
932 get_comment (const char *filename
, const char *fieldname
, char **ap
,
937 char c
, buffer
[BUFSIZ
], *dp
;
947 advise (NULL
, "invalid comment in message %s's %s: field",
948 filename
, fieldname
);
953 if ((c
= *cp
++) == '\0')
976 if ((dp
= *commentp
)) {
977 *commentp
= concat (dp
, " ", buffer
, NULL
);
980 *commentp
= add (buffer
, NULL
);
984 while (isspace ((unsigned char) *cp
))
995 * Handles content types audio, image, and video.
996 * There's not much to do right here.
1004 return OK
; /* not much to do here */
1015 char buffer
[BUFSIZ
];
1020 CI ci
= &ct
->c_ctinfo
;
1022 /* check for missing subtype */
1023 if (!*ci
->ci_subtype
)
1024 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1027 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1029 /* allocate text character set structure */
1031 ct
->c_ctparams
= (void *) t
;
1033 /* scan for charset parameter */
1034 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1035 if (!strcasecmp (pm
->pm_name
, "charset"))
1038 /* check if content specified a character set */
1040 chset
= pm
->pm_value
;
1041 t
->tx_charset
= CHARSET_SPECIFIED
;
1043 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1047 * If we can not handle character set natively,
1048 * then check profile for string to modify the
1049 * terminal or display method.
1051 * termproc is for mhshow, though mhlist -debug prints it, too.
1053 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1054 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1055 if ((cp
= context_find (buffer
)))
1056 ct
->c_termproc
= getcpy (cp
);
1068 InitMultiPart (CT ct
)
1078 struct multipart
*m
;
1079 struct part
*part
, **next
;
1080 CI ci
= &ct
->c_ctinfo
;
1085 * The encoding for multipart messages must be either
1086 * 7bit, 8bit, or binary (per RFC2045).
1088 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1089 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1090 /* Copy the Content-Transfer-Encoding header field body so we can
1091 remove any trailing whitespace and leading blanks from it. */
1092 char *cte
= add (ct
->c_celine
? ct
->c_celine
: "(null)", NULL
);
1094 bp
= cte
+ strlen (cte
) - 1;
1095 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1096 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1099 "\"%s/%s\" type in message %s must be encoded in\n"
1100 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1101 "mhfixmsg -fixcte can fix it, or\n"
1102 "manually edit the file and change the \"%s\"\n"
1103 "Content-Transfer-Encoding to one of those. For now",
1104 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1111 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1114 * Check for "boundary" parameter, which is
1115 * required for multipart messages.
1118 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1119 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1125 /* complain if boundary parameter is missing */
1128 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1129 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1133 /* allocate primary structure for multipart info */
1135 ct
->c_ctparams
= (void *) m
;
1137 /* check if boundary parameter contains only whitespace characters */
1138 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1141 advise (NULL
, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1142 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1146 /* remove trailing whitespace from boundary parameter */
1147 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1148 if (!isspace ((unsigned char) *dp
))
1152 /* record boundary separators */
1153 m
->mp_start
= concat (bp
, "\n", NULL
);
1154 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1156 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1157 advise (ct
->c_file
, "unable to open for reading");
1161 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1163 next
= &m
->mp_parts
;
1167 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1172 if (bufp
[0] != '-' || bufp
[1] != '-')
1175 if (strcmp (bufp
+ 2, m
->mp_start
))
1180 next
= &part
->mp_next
;
1182 if (!(p
= get_content (fp
, ct
->c_file
,
1183 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1191 fseek (fp
, pos
, SEEK_SET
);
1194 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1198 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1199 if (p
->c_end
< p
->c_begin
)
1200 p
->c_begin
= p
->c_end
;
1205 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1211 if (! suppress_bogus_mp_content_warning
) {
1212 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1214 bogus_mp_content
= 1;
1216 if (!inout
&& part
) {
1218 p
->c_end
= ct
->c_end
;
1220 if (p
->c_begin
>= p
->c_end
) {
1221 for (next
= &m
->mp_parts
; *next
!= part
;
1222 next
= &((*next
)->mp_next
))
1226 free ((char *) part
);
1231 /* reverse the order of the parts for multipart/alternative */
1232 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1238 * label all subparts with part number, and
1239 * then initialize the content of the subpart.
1244 char partnam
[BUFSIZ
];
1247 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1248 pp
= partnam
+ strlen (partnam
);
1253 for (part
= m
->mp_parts
, partnum
= 1; part
;
1254 part
= part
->mp_next
, partnum
++) {
1257 sprintf (pp
, "%d", partnum
);
1258 p
->c_partno
= add (partnam
, NULL
);
1260 /* initialize the content of the subparts */
1261 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1270 get_leftover_mp_content (ct
, 1);
1271 get_leftover_mp_content (ct
, 0);
1281 * reverse the order of the parts of a multipart/alternative,
1282 * presumably to put the "most favored" alternative first, for
1283 * ease of choosing/displaying it later on. from a mail message on
1284 * nmh-workers, from kenh:
1285 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1286 * see code in mhn that did the same thing... Acccording to the RCS
1287 * logs, that code was around from the initial checkin of mhn.c by
1288 * John Romine in 1992, which is as far back as we have."
1291 reverse_parts (CT ct
)
1293 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1297 /* Reverse the order of its parts by walking the mp_parts list
1298 and pushing each node to the front. */
1299 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1300 next
= part
->mp_next
;
1301 part
->mp_next
= m
->mp_parts
;
1307 move_preferred_part (CT ct
, char *type
, char *subtype
)
1309 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1310 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1314 /* move the matching part(s) to the head of the list: walk the
1315 * list of parts, move matching parts to a new list (maintaining
1316 * their order), and finally, concatenate the old list onto the
1323 head
->mp_next
= m
->mp_parts
;
1324 nhead
->mp_next
= NULL
;
1328 part
= head
->mp_next
;
1329 while (part
!= NULL
) {
1330 ci
= &part
->mp_part
->c_ctinfo
;
1331 if (!strcasecmp(ci
->ci_type
, type
) &&
1332 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1333 prev
->mp_next
= part
->mp_next
;
1334 part
->mp_next
= NULL
;
1335 ntail
->mp_next
= part
;
1337 part
= prev
->mp_next
;
1340 part
= prev
->mp_next
;
1343 ntail
->mp_next
= head
->mp_next
;
1344 m
->mp_parts
= nhead
->mp_next
;
1349 * move parts that match the user's preferences (-prefer) to the head
1350 * of the line. process preferences in reverse so first one given
1351 * ends up first in line
1357 for (i
= npreferred
-1; i
>= 0; i
--)
1358 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1363 /* parse_mime() arranges alternates in reverse (priority) order. This
1364 function can be used to reverse them back. This will put, for
1365 example, a text/plain part before a text/html part in a
1366 multipart/alternative part, for example, where it belongs. */
1368 reverse_alternative_parts (CT ct
) {
1369 if (ct
->c_type
== CT_MULTIPART
) {
1370 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1373 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1377 /* And call recursively on each part of a multipart. */
1378 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1379 reverse_alternative_parts (part
->mp_part
);
1392 CI ci
= &ct
->c_ctinfo
;
1394 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1396 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1397 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1401 /* check for missing subtype */
1402 if (!*ci
->ci_subtype
)
1403 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1406 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1408 switch (ct
->c_subtype
) {
1409 case MESSAGE_RFC822
:
1412 case MESSAGE_PARTIAL
:
1418 ct
->c_ctparams
= (void *) p
;
1420 /* scan for parameters "id", "number", and "total" */
1421 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1422 if (!strcasecmp (pm
->pm_name
, "id")) {
1423 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1426 if (!strcasecmp (pm
->pm_name
, "number")) {
1427 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1428 || p
->pm_partno
< 1) {
1431 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1432 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1433 ct
->c_file
, TYPE_FIELD
);
1438 if (!strcasecmp (pm
->pm_name
, "total")) {
1439 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1448 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1450 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1451 ci
->ci_type
, ci
->ci_subtype
,
1452 ct
->c_file
, TYPE_FIELD
);
1458 case MESSAGE_EXTERNAL
:
1466 ct
->c_ctparams
= (void *) e
;
1469 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1470 advise (ct
->c_file
, "unable to open for reading");
1474 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1476 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1484 p
->c_ceopenfnx
= NULL
;
1485 if ((exresult
= params_external (ct
, 0)) != NOTOK
1486 && p
->c_ceopenfnx
== openMail
) {
1490 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1492 content_error (NULL
, ct
,
1493 "empty body for access-type=mail-server");
1497 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1498 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1500 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1502 adios ("failed", "fread");
1505 adios (NULL
, "unexpected EOF from fread");
1508 bp
+= cc
, size
-= cc
;
1515 p
->c_end
= p
->c_begin
;
1520 if (exresult
== NOTOK
)
1522 if (e
->eb_flags
== NOTOK
)
1525 switch (p
->c_type
) {
1530 if (p
->c_subtype
!= MESSAGE_RFC822
)
1534 e
->eb_partno
= ct
->c_partno
;
1536 (*p
->c_ctinitfnx
) (p
);
1551 params_external (CT ct
, int composing
)
1554 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1555 CI ci
= &ct
->c_ctinfo
;
1557 ct
->c_ceopenfnx
= NULL
;
1558 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1559 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1560 struct str2init
*s2i
;
1561 CT p
= e
->eb_content
;
1563 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1564 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1567 e
->eb_access
= pm
->pm_value
;
1568 e
->eb_flags
= NOTOK
;
1569 p
->c_encoding
= CE_EXTERNAL
;
1572 e
->eb_access
= s2i
->si_key
;
1573 e
->eb_flags
= s2i
->si_val
;
1574 p
->c_encoding
= CE_EXTERNAL
;
1576 /* Call the Init function for this external type */
1577 if ((*s2i
->si_init
)(p
) == NOTOK
)
1581 if (!strcasecmp (pm
->pm_name
, "name")) {
1582 e
->eb_name
= pm
->pm_value
;
1585 if (!strcasecmp (pm
->pm_name
, "permission")) {
1586 e
->eb_permission
= pm
->pm_value
;
1589 if (!strcasecmp (pm
->pm_name
, "site")) {
1590 e
->eb_site
= pm
->pm_value
;
1593 if (!strcasecmp (pm
->pm_name
, "directory")) {
1594 e
->eb_dir
= pm
->pm_value
;
1597 if (!strcasecmp (pm
->pm_name
, "mode")) {
1598 e
->eb_mode
= pm
->pm_value
;
1601 if (!strcasecmp (pm
->pm_name
, "size")) {
1602 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1605 if (!strcasecmp (pm
->pm_name
, "server")) {
1606 e
->eb_server
= pm
->pm_value
;
1609 if (!strcasecmp (pm
->pm_name
, "subject")) {
1610 e
->eb_subject
= pm
->pm_value
;
1613 if (!strcasecmp (pm
->pm_name
, "url")) {
1615 * According to RFC 2017, we have to remove all whitespace from
1619 char *u
, *p
= pm
->pm_value
;
1620 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1622 for (; *p
!= '\0'; p
++) {
1623 if (! isspace((unsigned char) *p
))
1630 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1631 e
->eb_body
= getcpy (pm
->pm_value
);
1636 if (!e
->eb_access
) {
1638 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1639 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1652 InitApplication (CT ct
)
1654 CI ci
= &ct
->c_ctinfo
;
1657 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1664 * TRANSFER ENCODINGS
1668 init_encoding (CT ct
, OpenCEFunc openfnx
)
1670 ct
->c_ceopenfnx
= openfnx
;
1671 ct
->c_ceclosefnx
= close_encoding
;
1672 ct
->c_cesizefnx
= size_encoding
;
1679 close_encoding (CT ct
)
1681 CE ce
= &ct
->c_cefile
;
1690 static unsigned long
1691 size_encoding (CT ct
)
1696 CE ce
= &ct
->c_cefile
;
1699 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1700 return (long) st
.st_size
;
1703 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1704 return (long) st
.st_size
;
1709 if (ct
->c_encoding
== CE_EXTERNAL
)
1710 return (ct
->c_end
- ct
->c_begin
);
1713 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1714 return (ct
->c_end
- ct
->c_begin
);
1716 if (fstat (fd
, &st
) != NOTOK
)
1717 size
= (long) st
.st_size
;
1721 (*ct
->c_ceclosefnx
) (ct
);
1733 return init_encoding (ct
, openBase64
);
1738 openBase64 (CT ct
, char **file
)
1741 int fd
, own_ct_fp
= 0;
1742 char *cp
, *buffer
= NULL
;
1743 /* sbeck -- handle suffixes */
1745 CE ce
= &ct
->c_cefile
;
1746 unsigned char *decoded
;
1748 unsigned char digest
[16];
1751 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1756 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1757 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1763 if (*file
== NULL
) {
1766 ce
->ce_file
= add (*file
, NULL
);
1770 /* sbeck@cise.ufl.edu -- handle suffixes */
1772 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1773 if (ce
->ce_unlink
) {
1774 /* Create temporary file with filename extension. */
1775 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1776 adios(NULL
, "unable to create temporary file in %s",
1780 ce
->ce_file
= add (cp
, ce
->ce_file
);
1782 } else if (*file
== NULL
) {
1784 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1785 adios(NULL
, "unable to create temporary file in %s",
1788 ce
->ce_file
= add (tempfile
, NULL
);
1791 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1792 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1796 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1797 adios (NULL
, "internal error(1)");
1799 buffer
= mh_xmalloc (len
+ 1);
1802 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1803 content_error (ct
->c_file
, ct
, "unable to open for reading");
1809 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1812 switch (cc
= read (fd
, cp
, len
)) {
1814 content_error (ct
->c_file
, ct
, "error reading from");
1818 content_error (NULL
, ct
, "premature eof");
1829 /* decodeBase64() requires null-terminated input. */
1832 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1833 ct
->c_digested
? digest
: NULL
) == OK
) {
1835 unsigned char *decoded_p
= decoded
;
1836 for (i
= 0; i
< decoded_len
; ++i
) {
1837 putc (*decoded_p
++, ce
->ce_fp
);
1839 free ((char *) decoded
);
1840 if (ferror (ce
->ce_fp
)) {
1841 content_error (ce
->ce_file
, ct
, "error writing to");
1845 if (ct
->c_digested
) {
1846 if (memcmp(digest
, ct
->c_digest
,
1847 sizeof(digest
) / sizeof(digest
[0]))) {
1848 content_error (NULL
, ct
,
1849 "content integrity suspect (digest mismatch) -- continuing");
1852 fprintf (stderr
, "content integrity confirmed\n");
1860 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1862 if (fflush (ce
->ce_fp
)) {
1863 content_error (ce
->ce_file
, ct
, "error writing to");
1867 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1870 *file
= ce
->ce_file
;
1876 return fileno (ce
->ce_fp
);
1883 free_encoding (ct
, 0);
1893 static char hex2nib
[0x80] = {
1894 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1895 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1896 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1897 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1898 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1899 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1900 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1901 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1902 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1909 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1916 return init_encoding (ct
, openQuoted
);
1921 openQuoted (CT ct
, char **file
)
1923 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1929 CE ce
= &ct
->c_cefile
;
1930 /* sbeck -- handle suffixes */
1935 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1940 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1941 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1947 if (*file
== NULL
) {
1950 ce
->ce_file
= add (*file
, NULL
);
1954 /* sbeck@cise.ufl.edu -- handle suffixes */
1956 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1957 if (ce
->ce_unlink
) {
1958 /* Create temporary file with filename extension. */
1959 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1960 adios(NULL
, "unable to create temporary file in %s",
1964 ce
->ce_file
= add (cp
, ce
->ce_file
);
1966 } else if (*file
== NULL
) {
1968 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1969 adios(NULL
, "unable to create temporary file in %s",
1972 ce
->ce_file
= add (tempfile
, NULL
);
1975 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1976 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1980 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1981 adios (NULL
, "internal error(2)");
1984 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1985 content_error (ct
->c_file
, ct
, "unable to open for reading");
1991 if ((digested
= ct
->c_digested
))
1992 MD5Init (&mdContext
);
1999 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2001 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2002 content_error (NULL
, ct
, "premature eof");
2006 if ((cc
= gotlen
) > len
)
2010 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2011 if (!isspace ((unsigned char) *ep
))
2015 for (; cp
< ep
; cp
++) {
2017 /* in an escape sequence */
2019 /* at byte 1 of an escape sequence */
2020 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2021 /* next is byte 2 */
2024 /* at byte 2 of an escape sequence */
2026 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2027 putc (mask
, ce
->ce_fp
);
2029 MD5Update (&mdContext
, &mask
, 1);
2030 if (ferror (ce
->ce_fp
)) {
2031 content_error (ce
->ce_file
, ct
, "error writing to");
2034 /* finished escape sequence; next may be literal or a new
2035 * escape sequence */
2038 /* on to next byte */
2042 /* not in an escape sequence */
2044 /* starting an escape sequence, or invalid '='? */
2045 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2046 /* "=\n" soft line break, eat the \n */
2050 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2051 /* We don't have 2 bytes left, so this is an invalid
2052 * escape sequence; just show the raw bytes (below). */
2053 } else if (isxdigit ((unsigned char) cp
[1]) &&
2054 isxdigit ((unsigned char) cp
[2])) {
2055 /* Next 2 bytes are hex digits, making this a valid escape
2056 * sequence; let's decode it (above). */
2060 /* One or both of the next 2 is out of range, making this
2061 * an invalid escape sequence; just show the raw bytes
2066 /* Just show the raw byte. */
2067 putc (*cp
, ce
->ce_fp
);
2070 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2072 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2075 if (ferror (ce
->ce_fp
)) {
2076 content_error (ce
->ce_file
, ct
, "error writing to");
2082 content_error (NULL
, ct
,
2083 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2087 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2089 if (fflush (ce
->ce_fp
)) {
2090 content_error (ce
->ce_file
, ct
, "error writing to");
2095 unsigned char digest
[16];
2097 MD5Final (digest
, &mdContext
);
2098 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2099 sizeof(digest
) / sizeof(digest
[0])))
2100 content_error (NULL
, ct
,
2101 "content integrity suspect (digest mismatch) -- continuing");
2104 fprintf (stderr
, "content integrity confirmed\n");
2107 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2110 *file
= ce
->ce_file
;
2116 return fileno (ce
->ce_fp
);
2119 free_encoding (ct
, 0);
2136 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2139 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2145 open7Bit (CT ct
, char **file
)
2147 int cc
, fd
, len
, own_ct_fp
= 0;
2148 char buffer
[BUFSIZ
];
2149 /* sbeck -- handle suffixes */
2152 CE ce
= &ct
->c_cefile
;
2155 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2160 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2161 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2167 if (*file
== NULL
) {
2170 ce
->ce_file
= add (*file
, NULL
);
2174 /* sbeck@cise.ufl.edu -- handle suffixes */
2176 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2177 if (ce
->ce_unlink
) {
2178 /* Create temporary file with filename extension. */
2179 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2180 adios(NULL
, "unable to create temporary file in %s",
2184 ce
->ce_file
= add (cp
, ce
->ce_file
);
2186 } else if (*file
== NULL
) {
2188 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2189 adios(NULL
, "unable to create temporary file in %s",
2192 ce
->ce_file
= add (tempfile
, NULL
);
2195 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2196 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2200 if (ct
->c_type
== CT_MULTIPART
) {
2201 CI ci
= &ct
->c_ctinfo
;
2205 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2206 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2207 + 1 + strlen (ci
->ci_subtype
);
2208 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2211 fputs (buffer
, ce
->ce_fp
);
2215 if (ci
->ci_comment
) {
2216 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2217 fputs ("\n\t", ce
->ce_fp
);
2221 putc (' ', ce
->ce_fp
);
2224 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2227 fprintf (ce
->ce_fp
, "\n");
2229 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2231 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2233 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2234 fprintf (ce
->ce_fp
, "\n");
2237 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2238 adios (NULL
, "internal error(3)");
2241 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2242 content_error (ct
->c_file
, ct
, "unable to open for reading");
2248 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2250 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2252 content_error (ct
->c_file
, ct
, "error reading from");
2256 content_error (NULL
, ct
, "premature eof");
2264 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2265 advise ("open7Bit", "fwrite");
2267 if (ferror (ce
->ce_fp
)) {
2268 content_error (ce
->ce_file
, ct
, "error writing to");
2273 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2275 if (fflush (ce
->ce_fp
)) {
2276 content_error (ce
->ce_file
, ct
, "error writing to");
2280 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2283 *file
= ce
->ce_file
;
2288 return fileno (ce
->ce_fp
);
2291 free_encoding (ct
, 0);
2305 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2307 char cachefile
[BUFSIZ
];
2310 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2315 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2316 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2322 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2323 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2324 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2325 ce
->ce_file
= getcpy (cachefile
);
2329 admonish (cachefile
, "unable to fopen for reading");
2333 *fd
= fileno (ce
->ce_fp
);
2337 *file
= ce
->ce_file
;
2338 *fd
= fileno (ce
->ce_fp
);
2349 return init_encoding (ct
, openFile
);
2354 openFile (CT ct
, char **file
)
2357 char cachefile
[BUFSIZ
];
2358 struct exbody
*e
= ct
->c_ctexbody
;
2359 CE ce
= &ct
->c_cefile
;
2361 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2373 content_error (NULL
, ct
, "missing name parameter");
2377 ce
->ce_file
= getcpy (e
->eb_name
);
2380 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2381 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2385 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2386 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2387 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2391 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2392 if ((fp
= fopen (cachefile
, "w"))) {
2394 char buffer
[BUFSIZ
];
2395 FILE *gp
= ce
->ce_fp
;
2397 fseek (gp
, 0L, SEEK_SET
);
2399 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2401 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2402 advise ("openFile", "fwrite");
2407 admonish (ce
->ce_file
, "error reading");
2408 (void) m_unlink (cachefile
);
2412 admonish (cachefile
, "error writing");
2413 (void) m_unlink (cachefile
);
2420 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2421 *file
= ce
->ce_file
;
2422 return fileno (ce
->ce_fp
);
2432 return init_encoding (ct
, openFTP
);
2437 openFTP (CT ct
, char **file
)
2439 int cachetype
, caching
, fd
;
2441 char *bp
, *ftp
, *user
, *pass
;
2442 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2444 CE ce
= &ct
->c_cefile
;
2445 static char *username
= NULL
;
2446 static char *password
= NULL
;
2450 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2456 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2467 if (!e
->eb_name
|| !e
->eb_site
) {
2468 content_error (NULL
, ct
, "missing %s parameter",
2469 e
->eb_name
? "site": "name");
2473 /* Get the buffer ready to go */
2475 buflen
= sizeof(buffer
);
2478 * Construct the query message for user
2480 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2486 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2492 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2493 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2498 if (e
->eb_size
> 0) {
2499 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2504 snprintf (bp
, buflen
, "? ");
2507 * Now, check the answer
2509 if (!read_yes_or_no_if_tty (buffer
))
2514 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2518 ruserpass (e
->eb_site
, &username
, &password
);
2523 ce
->ce_unlink
= (*file
== NULL
);
2525 cachefile
[0] = '\0';
2526 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2527 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2528 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2529 if (*file
== NULL
) {
2536 ce
->ce_file
= add (*file
, NULL
);
2538 ce
->ce_file
= add (cachefile
, NULL
);
2541 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2542 adios(NULL
, "unable to create temporary file in %s",
2545 ce
->ce_file
= add (tempfile
, NULL
);
2548 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2549 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2554 int child_id
, i
, vecp
;
2558 vec
[vecp
++] = r1bindex (ftp
, '/');
2559 vec
[vecp
++] = e
->eb_site
;
2562 vec
[vecp
++] = e
->eb_dir
;
2563 vec
[vecp
++] = e
->eb_name
;
2564 vec
[vecp
++] = ce
->ce_file
,
2565 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2566 ? "ascii" : "binary";
2571 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2575 adios ("fork", "unable to");
2579 close (fileno (ce
->ce_fp
));
2581 fprintf (stderr
, "unable to exec ");
2587 if (pidXwait (child_id
, NULL
)) {
2588 username
= password
= NULL
;
2598 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2603 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2604 if ((fp
= fopen (cachefile
, "w"))) {
2606 FILE *gp
= ce
->ce_fp
;
2608 fseek (gp
, 0L, SEEK_SET
);
2610 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2612 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2613 advise ("openFTP", "fwrite");
2618 admonish (ce
->ce_file
, "error reading");
2619 (void) m_unlink (cachefile
);
2623 admonish (cachefile
, "error writing");
2624 (void) m_unlink (cachefile
);
2632 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2633 *file
= ce
->ce_file
;
2634 return fileno (ce
->ce_fp
);
2645 return init_encoding (ct
, openMail
);
2650 openMail (CT ct
, char **file
)
2652 int child_id
, fd
, i
, vecp
;
2654 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2655 struct exbody
*e
= ct
->c_ctexbody
;
2656 CE ce
= &ct
->c_cefile
;
2658 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2669 if (!e
->eb_server
) {
2670 content_error (NULL
, ct
, "missing server parameter");
2674 /* Get buffer ready to go */
2676 buflen
= sizeof(buffer
);
2678 /* Now, construct query message */
2679 snprintf (bp
, buflen
, "Retrieve content");
2685 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2691 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2693 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2695 /* Now, check answer */
2696 if (!read_yes_or_no_if_tty (buffer
))
2700 vec
[vecp
++] = r1bindex (mailproc
, '/');
2701 vec
[vecp
++] = e
->eb_server
;
2702 vec
[vecp
++] = "-subject";
2703 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2704 vec
[vecp
++] = "-body";
2705 vec
[vecp
++] = e
->eb_body
;
2708 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2712 advise ("fork", "unable to");
2716 execvp (mailproc
, vec
);
2717 fprintf (stderr
, "unable to exec ");
2723 if (pidXwait (child_id
, NULL
) == OK
)
2724 advise (NULL
, "request sent");
2728 if (*file
== NULL
) {
2730 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2731 adios(NULL
, "unable to create temporary file in %s",
2734 ce
->ce_file
= add (tempfile
, NULL
);
2737 ce
->ce_file
= add (*file
, NULL
);
2741 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2742 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2746 /* showproc is for mhshow and mhstore, though mhlist -debug
2747 * prints it, too. */
2749 free (ct
->c_showproc
);
2750 ct
->c_showproc
= add ("true", NULL
);
2752 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2753 *file
= ce
->ce_file
;
2754 return fileno (ce
->ce_fp
);
2765 return init_encoding (ct
, openURL
);
2770 openURL (CT ct
, char **file
)
2772 struct exbody
*e
= ct
->c_ctexbody
;
2773 CE ce
= &ct
->c_cefile
;
2774 char *urlprog
, *program
;
2775 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2776 int fd
, caching
, cachetype
;
2777 struct msgs_array args
= { 0, 0, NULL
};
2780 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2784 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2788 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2800 content_error(NULL
, ct
, "missing url parameter");
2804 ce
->ce_unlink
= (*file
== NULL
);
2806 cachefile
[0] = '\0';
2808 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2809 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2810 if (*file
== NULL
) {
2817 ce
->ce_file
= add(*file
, NULL
);
2819 ce
->ce_file
= add(cachefile
, NULL
);
2822 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2823 adios(NULL
, "unable to create temporary file in %s",
2826 ce
->ce_file
= add (tempfile
, NULL
);
2829 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2830 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2834 switch (child_id
= fork()) {
2836 adios ("fork", "unable to");
2840 argsplit_msgarg(&args
, urlprog
, &program
);
2841 app_msgarg(&args
, e
->eb_url
);
2842 app_msgarg(&args
, NULL
);
2843 dup2(fileno(ce
->ce_fp
), 1);
2844 close(fileno(ce
->ce_fp
));
2845 execvp(program
, args
.msgs
);
2846 fprintf(stderr
, "Unable to exec ");
2852 if (pidXwait(child_id
, NULL
)) {
2860 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2865 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2866 if ((fp
= fopen(cachefile
, "w"))) {
2868 FILE *gp
= ce
->ce_fp
;
2870 fseeko(gp
, 0, SEEK_SET
);
2872 while ((cc
= fread(buffer
, sizeof(*buffer
),
2873 sizeof(buffer
), gp
)) > 0)
2874 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2875 advise ("openURL", "fwrite");
2881 admonish(ce
->ce_file
, "error reading");
2882 (void) m_unlink (cachefile
);
2889 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2890 *file
= ce
->ce_file
;
2896 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2897 * has to be base64 decoded.
2900 readDigest (CT ct
, char *cp
)
2902 unsigned char *digest
;
2905 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2906 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2908 if (strlen ((char *) digest
) <= maxlen
) {
2909 memcpy (ct
->c_digest
, digest
, maxlen
);
2914 fprintf (stderr
, "MD5 digest=");
2915 for (i
= 0; i
< maxlen
; ++i
) {
2916 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2918 fprintf (stderr
, "\n");
2924 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2925 (int) strlen ((char *) digest
));
2936 /* Multipart parts might have content before the first subpart and/or
2937 after the last subpart that hasn't been stored anywhere else, so do
2940 get_leftover_mp_content (CT ct
, int before
/* or after */)
2942 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2944 int found_boundary
= 0;
2950 char *content
= NULL
;
2952 if (! m
) return NOTOK
;
2955 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2957 /* Isolate the beginning of this part to the beginning of the
2958 first subpart and save any content between them. */
2959 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2960 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2961 boundary
= concat ("--", m
->mp_start
, NULL
);
2963 struct part
*last_subpart
= NULL
;
2964 struct part
*subpart
;
2966 /* Go to the last subpart to get its end position. */
2967 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2968 last_subpart
= subpart
;
2971 if (last_subpart
== NULL
) return NOTOK
;
2973 /* Isolate the end of the last subpart to the end of this part
2974 and save any content between them. */
2975 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2976 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2977 boundary
= concat ("--", m
->mp_stop
, NULL
);
2980 /* Back up by 1 to pick up the newline. */
2981 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2983 /* Don't look beyond beginning of first subpart (before) or
2984 next part (after). */
2985 if (read
> max
) bufp
[read
-max
] = '\0';
2988 if (! strcmp (bufp
, boundary
)) {
2992 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2998 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3000 char *old_content
= content
;
3001 content
= concat (content
, bufp
, NULL
);
3005 ? concat ("\n", bufp
, NULL
)
3006 : concat (bufp
, NULL
);
3011 if (found_boundary
|| read
> max
) break;
3013 if (read
> max
) break;
3017 /* Skip the newline if that's all there is. */
3021 /* Remove trailing newline, except at EOF. */
3022 if ((before
|| ! feof (ct
->c_fp
)) &&
3023 (cp
= content
+ strlen (content
)) > content
&&
3028 if (strlen (content
) > 1) {
3030 m
->mp_content_before
= content
;
3032 m
->mp_content_after
= content
;
3047 ct_type_str (int type
) {
3049 case CT_APPLICATION
:
3050 return "application";
3066 return "unknown_type";
3072 ct_subtype_str (int type
, int subtype
) {
3074 case CT_APPLICATION
:
3076 case APPLICATION_OCTETS
:
3078 case APPLICATION_POSTSCRIPT
:
3079 return "postscript";
3081 return "unknown_app_subtype";
3085 case MESSAGE_RFC822
:
3087 case MESSAGE_PARTIAL
:
3089 case MESSAGE_EXTERNAL
:
3092 return "unknown_msg_subtype";
3098 case MULTI_ALTERNATE
:
3099 return "alternative";
3102 case MULTI_PARALLEL
:
3107 return "unknown_multipart_subtype";
3118 return "unknown_text_subtype";
3121 return "unknown_type";
3127 ct_str_type (const char *type
) {
3128 struct str2init
*s2i
;
3130 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3131 if (! strcasecmp (type
, s2i
->si_key
)) {
3135 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3144 ct_str_subtype (int type
, const char *subtype
) {
3148 case CT_APPLICATION
:
3149 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3150 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3154 return kv
->kv_value
;
3156 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3157 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3161 return kv
->kv_value
;
3163 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3164 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3168 return kv
->kv_value
;
3170 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3171 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3175 return kv
->kv_value
;
3182 /* Find the content type and InitFunc for the CT. */
3183 const struct str2init
*
3184 get_ct_init (int type
) {
3185 const struct str2init
*sp
;
3187 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3188 if (type
== sp
->si_val
) {
3197 ce_str (int encoding
) {
3202 return "quoted-printable";
3218 /* Find the content type and InitFunc for the content encoding method. */
3219 const struct str2init
*
3220 get_ce_method (const char *method
) {
3221 struct str2init
*sp
;
3223 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3224 if (! strcasecmp (method
, sp
->si_key
)) {
3233 * Parse a series of MIME attributes (or parameters) given a header as
3236 * Arguments include:
3238 * filename - Name of input file (for error messages)
3239 * fieldname - Name of field being processed
3240 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3241 * Updated to point to end of attributes when finished.
3242 * param_head - Pointer to head of parameter list
3243 * param_tail - Pointer to tail of parameter list
3244 * commentp - Pointer to header comment pointer (may be NULL)
3246 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3247 * DONE to indicate a benign error (minor parsing error, but the program
3252 parse_header_attrs (const char *filename
, const char *fieldname
,
3253 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3256 char *cp
= *header_attrp
;
3262 struct sectlist
*next
;
3268 struct sectlist
*sechead
;
3269 struct parmlist
*next
;
3270 } *pp
, *pp2
, *phead
= NULL
;
3272 while (*cp
== ';') {
3273 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3274 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3277 while (isspace ((unsigned char) *cp
))
3281 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3286 if (! suppress_extraneous_trailing_semicolon_warning
) {
3288 "extraneous trailing ';' in message %s's %s: "
3290 filename
, fieldname
);
3292 extraneous_trailing_semicolon
= 1;
3296 /* down case the attribute name */
3297 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3298 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
3299 *dp
= tolower ((unsigned char) *dp
);
3301 for (up
= dp
; isspace ((unsigned char) *dp
);)
3303 if (dp
== cp
|| *dp
!= '=') {
3305 "invalid parameter in message %s's %s: "
3306 "field\n%*sparameter %s (error detected at offset %d)",
3307 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3312 * To handle RFC 2231, we have to deal with the following extensions:
3314 * name*=encoded-value
3315 * name*<N>=part-N-of-a-parameter-value
3316 * name*<N>*=encoded-part-N-of-a-parameter-value
3319 * If there's a * right before the equal sign, it's encoded.
3320 * If there's a * and one or more digits, then it's section N.
3322 * Remember we can have one or the other, or both. cp points to
3323 * beginning of name, up points past the last character in the
3327 for (vp
= cp
; vp
< up
; vp
++) {
3328 if (*vp
== '*' && vp
< up
- 1) {
3331 } else if (*vp
== '*' && vp
== up
- 1) {
3333 } else if (partial
) {
3334 if (isdigit((unsigned char) *vp
))
3335 index
= *vp
- '0' + index
* 10;
3337 advise (NULL
, "invalid parameter index in message %s's "
3338 "%s: field\n%*s(parameter %s)", filename
,
3339 fieldname
, strlen(invo_name
) + 2, "", cp
);
3348 * Break out the parameter name and value sections and allocate
3352 nameptr
= mh_xmalloc(len
+ 1);
3353 strncpy(nameptr
, cp
, len
);
3354 nameptr
[len
] = '\0';
3356 for (dp
++; isspace ((unsigned char) *dp
);)
3361 * Single quotes delimit the character set and language tag.
3362 * They are required on the first section (or a complete
3367 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3373 charset
= mh_xmalloc(len
+ 1);
3374 strncpy(charset
, dp
, len
);
3375 charset
[len
] = '\0';
3381 advise(NULL
, "missing charset in message %s's %s: "
3382 "field\n%*s(parameter %s)", filename
, fieldname
,
3383 strlen(invo_name
) + 2, "", nameptr
);
3389 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3396 lang
= mh_xmalloc(len
+ 1);
3397 strncpy(lang
, dp
, len
);
3404 advise(NULL
, "missing language tag in message %s's %s: "
3405 "field\n%*s(parameter %s)", filename
, fieldname
,
3406 strlen(invo_name
) + 2, "", nameptr
);
3417 * At this point vp should be pointing at the beginning
3418 * of the encoded value/section. Continue until we reach
3419 * the end or get whitespace. But first, calculate the
3420 * length so we can allocate the correct buffer size.
3423 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3425 if (*(vp
+ 1) == '\0' ||
3426 !isxdigit((unsigned char) *(vp
+ 1)) ||
3427 *(vp
+ 2) == '\0' ||
3428 !isxdigit((unsigned char) *(vp
+ 2))) {
3429 advise(NULL
, "invalid encoded sequence in message "
3430 "%s's %s: field\n%*s(parameter %s)",
3431 filename
, fieldname
, strlen(invo_name
) + 2,
3445 up
= valptr
= mh_xmalloc(len
+ 1);
3447 for (vp
= dp
; istoken(*vp
); vp
++) {
3449 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3460 * A "normal" string. If it's got a leading quote, then we
3461 * strip the quotes out. Otherwise go until we reach the end
3462 * or get whitespace. Note we scan it twice; once to get the
3463 * length, then the second time copies it into the destination
3470 for (cp
= dp
+ 1;;) {
3475 "invalid quoted-string in message %s's %s: "
3476 "field\n%*s(parameter %s)",
3477 filename
, fieldname
, strlen(invo_name
) + 2, "",
3500 for (cp
= dp
; istoken (*cp
); cp
++) {
3505 valptr
= mh_xmalloc(len
+ 1);
3509 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3517 strncpy(valptr
, cp
= dp
, len
);
3525 * If 'partial' is set, we don't allocate a parameter now. We
3526 * put it on the parameter linked list to be reassembled later.
3528 * "phead" points to a list of all parameters we need to reassemble.
3529 * Each parameter has a list of sections. We insert the sections in
3534 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3535 if (strcasecmp(nameptr
, pp
->name
) == 0)
3547 * Insert this into the section linked list
3555 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3556 sp
->next
= pp
->sechead
;
3559 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3560 if (sp2
->index
== sp
->index
) {
3561 advise (NULL
, "duplicate index (%d) in message "
3562 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3563 filename
, fieldname
, strlen(invo_name
) + 2, "",
3568 if (sp2
->index
< sp
->index
&&
3569 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3570 sp
->next
= sp2
->next
;
3577 advise(NULL
, "Internal error: cannot insert partial "
3578 "param in message %s's %s: field\n%*s(parameter %s)",
3579 filename
, fieldname
, strlen(invo_name
) + 2, "",
3587 * Save our charset and lang tags.
3590 if (index
== 0 && encoded
) {
3593 pp
->charset
= charset
;
3599 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3600 pm
->pm_charset
= charset
;
3604 while (isspace ((unsigned char) *cp
))
3608 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3614 * Now that we're done, reassemble all of the partial parameters.
3617 for (pp
= phead
; pp
!= NULL
; ) {
3621 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3622 if (sp
->index
!= pindex
++) {
3623 advise(NULL
, "missing section %d for parameter in "
3624 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3625 filename
, fieldname
, strlen(invo_name
) + 2, "",
3632 p
= q
= mh_xmalloc(tlen
+ 1);
3633 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3634 memcpy(q
, sp
->value
, sp
->len
);
3644 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3645 pm
->pm_charset
= pp
->charset
;
3646 pm
->pm_lang
= pp
->lang
;
3657 * Return the charset for a particular content type.
3661 content_charset (CT ct
) {
3662 char *ret_charset
= NULL
;
3664 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3666 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3671 * Create a string based on a list of output parameters. Assume that this
3672 * parameter string will be appended to an existing header, so start out
3673 * with the separator (;). Perform RFC 2231 encoding when necessary.
3677 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3679 char *paramout
= NULL
;
3680 char line
[CPERLIN
* 2], *q
;
3681 int curlen
, index
, cont
, encode
, i
;
3682 size_t valoff
, numchars
;
3684 while (params
!= NULL
) {
3690 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3693 if (strlen(params
->pm_name
) > CPERLIN
) {
3694 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3700 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3703 * Loop until we get a parameter that fits within a line. We
3704 * assume new lines start with a tab, so check our overflow based
3714 * At this point we're definitely continuing the line, so
3715 * be sure to include the parameter name and section index.
3718 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3719 params
->pm_name
, index
);
3722 * Both of these functions do a NUL termination
3726 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3727 numchars
, valoff
, index
);
3729 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3740 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3745 * "line" starts with a ;\n\t, so that doesn't count against
3746 * the length. But add 8 since it starts with a tab; that's
3747 * how we end up with 5.
3750 initialwidth
= strlen(line
) + 5;
3753 * At this point the line should be built, so add it to our
3754 * current output buffer.
3757 paramout
= add(line
, paramout
);
3761 * If this won't fit on the line, start a new one. Save room in
3762 * case we need a semicolon on the end
3765 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3777 * At this point, we're either finishing a contined parameter, or
3778 * we're working on a new one.
3782 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3783 params
->pm_name
, index
);
3785 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3790 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3791 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3793 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3794 strlen(params
->pm_value
+ valoff
), valoff
);
3802 paramout
= add(line
, paramout
);
3803 initialwidth
+= strlen(line
);
3805 params
= params
->pm_next
;
3809 *offsetout
= initialwidth
;
3815 * Calculate the size of a parameter.
3819 * pm - The parameter being output
3820 * index - If continuing the parameter, the index of the section
3822 * valueoff - The current offset into the parameter value that we're
3823 * working on (previous sections have consumed valueoff bytes).
3824 * encode - Set if we should perform encoding on this parameter section
3825 * (given that we're consuming bytesfit bytes).
3826 * cont - Set if the remaining data in value will not fit on a single
3827 * line and will need to be continued.
3828 * bytesfit - The number of bytes that we can consume from the parameter
3829 * value and still fit on a completely new line. The
3830 * calculation assumes the new line starts with a tab,
3831 * includes the parameter name and any encoding, and fits
3832 * within CPERLIN bytes. Will always be at least 1.
3836 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3839 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3840 size_t len
= 0, fit
= 0;
3841 int fitlimit
= 0, eightbit
, maxfit
;
3846 * Add up the length. First, start with the parameter name.
3849 len
= strlen(pm
->pm_name
);
3852 * Scan the parameter value and see if we need to do encoding for this
3856 eightbit
= contains8bit(start
, NULL
);
3859 * Determine if we need to encode this section. Encoding is necessary if:
3861 * - There are any 8-bit characters at all and we're on the first
3863 * - There are 8-bit characters within N bytes of our section start.
3864 * N is calculated based on the number of bytes it would take to
3865 * reach CPERLIN. Specifically:
3866 * 8 (starting tab) +
3867 * strlen(param name) +
3868 * 4 ('* for section marker, '=', opening/closing '"')
3870 * is the number of bytes used by everything that isn't part of the
3871 * value. So that gets subtracted from CPERLIN.
3874 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3875 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3876 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3880 len
++; /* Add in equal sign */
3884 * We're using maxfit as a marker for how many characters we can
3885 * fit into the line. Bump it by two because we're not using quotes
3892 * If we don't have a charset or language tag in this parameter,
3896 if (! pm
->pm_charset
) {
3897 pm
->pm_charset
= getcpy(write_charset_8bit());
3898 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3899 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3900 "local character set is US-ASCII", pm
->pm_name
);
3903 pm
->pm_lang
= getcpy(NULL
); /* Default to a blank lang tag */
3905 len
++; /* For the encoding marker */
3908 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3913 * We know we definitely need to include an index. maxfit already
3914 * includes the section marker.
3916 len
+= strlen(indexchar
);
3918 for (p
= start
; *p
!= '\0'; p
++) {
3919 if (isparamencode(*p
)) {
3927 * Just so there's no confusion: maxfit is counting OUTPUT
3928 * characters (post-encoding). fit is counting INPUT characters.
3930 if (! fitlimit
&& maxfit
>= 0)
3932 else if (! fitlimit
)
3937 * Calculate the string length, but add room for quoting \
3938 * and " if necessary. Also account for quotes at beginning
3941 for (p
= start
; *p
!= '\0'; p
++) {
3952 if (! fitlimit
&& maxfit
>= 0)
3954 else if (! fitlimit
)
3971 * Output an encoded parameter string.
3975 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3976 size_t valueoff
, int index
)
3978 size_t outlen
= 0, n
;
3979 char *endptr
= output
+ len
, *p
;
3982 * First, output the marker for an encoded string.
3990 * If the index is 0, output the character set and language tag.
3991 * If theses were NULL, they should have already been filled in
3996 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
4000 if (output
> endptr
) {
4001 advise(NULL
, "Internal error: parameter buffer overflow");
4007 * Copy over the value, encoding if necessary
4010 p
= pm
->pm_value
+ valueoff
;
4011 while (valuelen
-- > 0) {
4012 if (isparamencode(*p
)) {
4013 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4020 if (output
> endptr
) {
4021 advise(NULL
, "Internal error: parameter buffer overflow");
4032 * Output a "normal" parameter, without encoding. Be sure to escape
4033 * quotes and backslashes if necessary.
4037 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4041 char *endptr
= output
+ len
, *p
;
4047 p
= pm
->pm_value
+ valueoff
;
4049 while (valuelen
-- > 0) {
4059 if (output
> endptr
) {
4060 advise(NULL
, "Internal error: parameter buffer overflow");
4065 if (output
- 2 > endptr
) {
4066 advise(NULL
, "Internal error: parameter buffer overflow");
4077 * Add a parameter to the parameter linked list
4081 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4086 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4087 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4090 (*last
)->pm_next
= pm
;
4101 * Either replace a current parameter with a new value, or add the parameter
4102 * to the parameter linked list.
4106 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4110 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4111 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4113 * If nocopy is set, it's assumed that we own both name
4114 * and value. We don't need name, so we discard it now.
4119 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4124 return add_param(first
, last
, name
, value
, nocopy
);
4128 * Retrieve a parameter value from a parameter linked list. If the parameter
4129 * value needs converted to the local character set, do that now.
4133 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4135 while (first
!= NULL
) {
4136 if (strcasecmp(name
, first
->pm_name
) == 0) {
4138 return first
->pm_value
;
4140 return getcpy(get_param_value(first
, replace
));
4142 first
= first
->pm_next
;
4149 * Return a parameter value, converting to the local character set if
4153 char *get_param_value(PM pm
, char replace
)
4155 static char buffer
[4096]; /* I hope no parameters are larger */
4156 size_t bufsize
= sizeof(buffer
);
4161 ICONV_CONST
char *p
;
4162 #else /* HAVE_ICONV */
4164 #endif /* HAVE_ICONV */
4169 * If we don't have a character set indicated, it's assumed to be
4170 * US-ASCII. If it matches our character set, we don't need to convert
4174 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4175 strlen(pm
->pm_charset
))) {
4176 return pm
->pm_value
;
4180 * In this case, we need to convert. If we have iconv support, use
4181 * that. Otherwise, go through and simply replace every non-ASCII
4182 * character with the substitution character.
4187 bufsize
= sizeof(buffer
);
4188 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4190 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4191 if (cd
== (iconv_t
) -1) {
4195 inbytes
= strlen(pm
->pm_value
);
4199 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4200 if (errno
!= EILSEQ
) {
4205 * Reset shift state, substitute our character,
4206 * try to restart conversion.
4209 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4222 for (++p
, --inbytes
;
4223 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4242 #endif /* HAVE_ICONV */
4245 * Take everything non-ASCII and substituite the replacement character
4249 bufsize
= sizeof(buffer
);
4250 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4251 if (isascii((unsigned char) *p
) && !iscntrl((unsigned char) *p
))