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 */
297 if (!(ct
= (CT
) mh_xcalloc (1, sizeof(*ct
))))
298 adios (NULL
, "out of memory");
301 ct
->c_file
= add (file
, NULL
);
302 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
305 * Parse the header fields for this
306 * content into a linked list.
308 m_getfld_track_filepos (&gstate
, in
);
309 for (compnum
= 1;;) {
310 int bufsz
= sizeof buf
;
311 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
316 /* get copies of the buffers */
317 np
= add (name
, NULL
);
318 vp
= add (buf
, NULL
);
320 /* if necessary, get rest of field */
321 while (state
== FLDPLUS
) {
323 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
324 vp
= add (buf
, vp
); /* add to previous value */
327 /* Now add the header data to the list */
328 add_header (ct
, np
, vp
);
330 /* continue, to see if this isn't the last header field */
331 ct
->c_begin
= ftell (in
) + 1;
335 ct
->c_begin
= ftell (in
) - strlen (buf
);
339 ct
->c_begin
= ftell (in
);
344 adios (NULL
, "message format error in component #%d", compnum
);
347 adios (NULL
, "getfld() returned %d", state
);
350 /* break out of the loop */
353 m_getfld_state_destroy (&gstate
);
356 * Read the content headers. We will parse the
357 * MIME related header fields into their various
358 * structures and set internal flags related to
359 * content type/subtype, etc.
362 hp
= ct
->c_first_hf
; /* start at first header field */
364 /* Get MIME-Version field */
365 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
370 vrsn
= add (hp
->value
, NULL
);
372 /* Now, cleanup this field */
375 while (isspace ((unsigned char) *cp
))
377 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
379 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
380 if (!isspace ((unsigned char) *dp
))
384 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
387 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
390 for (dp
= cp
; istoken (*dp
); dp
++)
394 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
397 admonish (NULL
, "message %s has unknown value for %s: field (%s)",
398 ct
->c_file
, VRSN_FIELD
, cp
);
403 if (! suppress_multiple_mime_version_warning
)
404 advise (NULL
, "message %s has multiple %s: fields",
405 ct
->c_file
, VRSN_FIELD
);
409 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
410 /* Get Content-Type field */
411 struct str2init
*s2i
;
412 CI ci
= &ct
->c_ctinfo
;
414 /* Check if we've already seen a Content-Type header */
416 advise (NULL
, "message %s has multiple %s: fields",
417 ct
->c_file
, TYPE_FIELD
);
421 /* Parse the Content-Type field */
422 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
426 * Set the Init function and the internal
427 * flag for this content type.
429 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
430 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
432 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
434 ct
->c_type
= s2i
->si_val
;
435 ct
->c_ctinitfnx
= s2i
->si_init
;
437 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
438 /* Get Content-Transfer-Encoding field */
440 struct str2init
*s2i
;
443 * Check if we've already seen the
444 * Content-Transfer-Encoding field
447 advise (NULL
, "message %s has multiple %s: fields",
448 ct
->c_file
, ENCODING_FIELD
);
452 /* get copy of this field */
453 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
455 while (isspace ((unsigned char) *cp
))
457 for (dp
= cp
; istoken (*dp
); dp
++)
463 * Find the internal flag and Init function
464 * for this transfer encoding.
466 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
467 if (!strcasecmp (cp
, s2i
->si_key
))
469 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
472 ct
->c_encoding
= s2i
->si_val
;
474 /* Call the Init function for this encoding */
475 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
478 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
479 /* Get Content-MD5 field */
485 if (ct
->c_digested
) {
486 advise (NULL
, "message %s has multiple %s: fields",
487 ct
->c_file
, MD5_FIELD
);
491 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
493 while (isspace ((unsigned char) *cp
))
495 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
497 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
498 if (!isspace ((unsigned char) *dp
))
502 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
505 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
510 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
518 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
519 /* Get Content-ID field */
520 ct
->c_id
= add (hp
->value
, ct
->c_id
);
522 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
523 /* Get Content-Description field */
524 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
526 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
527 /* Get Content-Disposition field */
528 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
533 hp
= hp
->next
; /* next header field */
537 * Check if we saw a Content-Type field.
538 * If not, then assign a default value for
539 * it, and the Init function.
543 * If we are inside a multipart/digest message,
544 * so default type is message/rfc822
547 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
549 ct
->c_type
= CT_MESSAGE
;
550 ct
->c_ctinitfnx
= InitMessage
;
553 * Else default type is text/plain
555 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
557 ct
->c_type
= CT_TEXT
;
558 ct
->c_ctinitfnx
= InitText
;
562 /* Use default Transfer-Encoding, if necessary */
564 ct
->c_encoding
= CE_7BIT
;
577 * small routine to add header field to list
581 add_header (CT ct
, char *name
, char *value
)
585 /* allocate header field structure */
586 hp
= mh_xmalloc (sizeof(*hp
));
588 /* link data into header structure */
593 /* link header structure into the list */
594 if (ct
->c_first_hf
== NULL
) {
595 ct
->c_first_hf
= hp
; /* this is the first */
598 ct
->c_last_hf
->next
= hp
; /* add it to the end */
607 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
608 * directives. Fills in the information of the CTinfo structure.
611 get_ctinfo (char *cp
, CT ct
, int magic
)
620 /* store copy of Content-Type line */
621 cp
= ct
->c_ctline
= add (cp
, NULL
);
623 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
626 /* change newlines to spaces */
627 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
630 /* trim trailing spaces */
631 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
632 if (!isspace ((unsigned char) *dp
))
637 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
639 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
640 &ci
->ci_comment
) == NOTOK
)
643 for (dp
= cp
; istoken (*dp
); dp
++)
646 ci
->ci_type
= add (cp
, NULL
); /* store content type */
650 advise (NULL
, "invalid %s: field in message %s (empty type)",
651 TYPE_FIELD
, ct
->c_file
);
655 /* down case the content type string */
656 for (dp
= ci
->ci_type
; *dp
; dp
++)
657 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
658 *dp
= tolower ((unsigned char) *dp
);
660 while (isspace ((unsigned char) *cp
))
663 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
664 &ci
->ci_comment
) == NOTOK
)
669 ci
->ci_subtype
= add ("", NULL
);
674 while (isspace ((unsigned char) *cp
))
677 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
678 &ci
->ci_comment
) == NOTOK
)
681 for (dp
= cp
; istoken (*dp
); dp
++)
684 ci
->ci_subtype
= add (cp
, NULL
); /* store the content subtype */
687 if (!*ci
->ci_subtype
) {
689 "invalid %s: field in message %s (empty subtype for \"%s\")",
690 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
694 /* down case the content subtype string */
695 for (dp
= ci
->ci_subtype
; *dp
; dp
++)
696 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
697 *dp
= tolower ((unsigned char) *dp
);
700 while (isspace ((unsigned char) *cp
))
703 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
704 &ci
->ci_comment
) == NOTOK
)
707 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
708 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
709 &ci
->ci_comment
)) != OK
) {
710 return status
== NOTOK
? NOTOK
: OK
;
714 * Get any <Content-Id> given in buffer
716 if (magic
&& *cp
== '<') {
721 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
722 advise (NULL
, "invalid ID in message %s", ct
->c_file
);
728 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
734 while (isspace ((unsigned char) *cp
))
739 * Get any [Content-Description] given in buffer.
741 if (magic
&& *cp
== '[') {
743 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
747 advise (NULL
, "invalid description in message %s", ct
->c_file
);
755 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
761 while (isspace ((unsigned char) *cp
))
766 * Get any {Content-Disposition} given in buffer.
768 if (magic
&& *cp
== '{') {
770 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
774 advise (NULL
, "invalid disposition in message %s", ct
->c_file
);
782 if (get_dispo(cp
, ct
, 1) != OK
)
788 while (isspace ((unsigned char) *cp
))
793 * Get any extension directives (right now just the content transfer
794 * encoding, but maybe others) that we care about.
797 if (magic
&& *cp
== '*') {
799 * See if it's a CTE we match on
804 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
808 advise (NULL
, "invalid null transfer encoding specification");
815 ct
->c_reqencoding
= CE_UNKNOWN
;
817 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
818 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
819 ct
->c_reqencoding
= kv
->kv_value
;
824 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
825 advise (NULL
, "invalid CTE specification: \"%s\"", dp
);
829 while (isspace ((unsigned char) *cp
))
834 * Check if anything is left over
838 ci
->ci_magic
= add (cp
, NULL
);
840 /* If there is a Content-Disposition header and it doesn't
841 have a *filename=, extract it from the magic contents.
842 The r1bindex call skips any leading directory
844 if (ct
->c_dispo_type
&&
845 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
846 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
847 r1bindex(ci
->ci_magic
, '/'), 0);
852 "extraneous information in message %s's %s: field\n%*s(%s)",
853 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
861 * Parse out a Content-Disposition header. A lot of this is cribbed from
865 get_dispo (char *cp
, CT ct
, int buildflag
)
867 char *dp
, *dispoheader
;
872 * Save the whole copy of the Content-Disposition header, unless we're
873 * processing a mhbuild directive. A NULL c_dispo will be a flag to
874 * mhbuild that the disposition header needs to be generated at that
878 dispoheader
= cp
= add(cp
, NULL
);
880 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
883 /* change newlines to spaces */
884 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
887 /* trim trailing spaces */
888 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
889 if (!isspace ((unsigned char) *dp
))
894 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
896 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
902 for (dp
= cp
; istoken (*dp
); dp
++)
905 ct
->c_dispo_type
= add (cp
, NULL
); /* store disposition type */
908 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
911 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
912 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
914 if (status
== NOTOK
) {
920 "extraneous information in message %s's %s: field\n%*s(%s)",
921 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
927 ct
->c_dispo
= dispoheader
;
934 get_comment (const char *filename
, const char *fieldname
, char **ap
,
939 char c
, buffer
[BUFSIZ
], *dp
;
949 advise (NULL
, "invalid comment in message %s's %s: field",
950 filename
, fieldname
);
955 if ((c
= *cp
++) == '\0')
978 if ((dp
= *commentp
)) {
979 *commentp
= concat (dp
, " ", buffer
, NULL
);
982 *commentp
= add (buffer
, NULL
);
986 while (isspace ((unsigned char) *cp
))
997 * Handles content types audio, image, and video.
998 * There's not much to do right here.
1006 return OK
; /* not much to do here */
1017 char buffer
[BUFSIZ
];
1022 CI ci
= &ct
->c_ctinfo
;
1024 /* check for missing subtype */
1025 if (!*ci
->ci_subtype
)
1026 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1029 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1031 /* allocate text character set structure */
1032 if ((t
= (struct text
*) mh_xcalloc (1, sizeof(*t
))) == NULL
)
1033 adios (NULL
, "out of memory");
1034 ct
->c_ctparams
= (void *) t
;
1036 /* scan for charset parameter */
1037 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1038 if (!strcasecmp (pm
->pm_name
, "charset"))
1041 /* check if content specified a character set */
1043 chset
= pm
->pm_value
;
1044 t
->tx_charset
= CHARSET_SPECIFIED
;
1046 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1050 * If we can not handle character set natively,
1051 * then check profile for string to modify the
1052 * terminal or display method.
1054 * termproc is for mhshow, though mhlist -debug prints it, too.
1056 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1057 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1058 if ((cp
= context_find (buffer
)))
1059 ct
->c_termproc
= getcpy (cp
);
1071 InitMultiPart (CT ct
)
1081 struct multipart
*m
;
1082 struct part
*part
, **next
;
1083 CI ci
= &ct
->c_ctinfo
;
1088 * The encoding for multipart messages must be either
1089 * 7bit, 8bit, or binary (per RFC2045).
1091 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1092 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1093 /* Copy the Content-Transfer-Encoding header field body so we can
1094 remove any trailing whitespace and leading blanks from it. */
1095 char *cte
= add (ct
->c_celine
? ct
->c_celine
: "(null)", NULL
);
1097 bp
= cte
+ strlen (cte
) - 1;
1098 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1099 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1102 "\"%s/%s\" type in message %s must be encoded in\n"
1103 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1104 "mhfixmsg -fixcte can fix it, or\n"
1105 "manually edit the file and change the \"%s\"\n"
1106 "Content-Transfer-Encoding to one of those. For now",
1107 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1114 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1117 * Check for "boundary" parameter, which is
1118 * required for multipart messages.
1121 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1122 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1128 /* complain if boundary parameter is missing */
1131 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1132 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1136 /* allocate primary structure for multipart info */
1137 if ((m
= (struct multipart
*) mh_xcalloc (1, sizeof(*m
))) == NULL
)
1138 adios (NULL
, "out of memory");
1139 ct
->c_ctparams
= (void *) m
;
1141 /* check if boundary parameter contains only whitespace characters */
1142 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1145 advise (NULL
, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1146 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1150 /* remove trailing whitespace from boundary parameter */
1151 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1152 if (!isspace ((unsigned char) *dp
))
1156 /* record boundary separators */
1157 m
->mp_start
= concat (bp
, "\n", NULL
);
1158 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1160 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1161 advise (ct
->c_file
, "unable to open for reading");
1165 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1167 next
= &m
->mp_parts
;
1171 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1176 if (bufp
[0] != '-' || bufp
[1] != '-')
1179 if (strcmp (bufp
+ 2, m
->mp_start
))
1182 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
1183 adios (NULL
, "out of memory");
1185 next
= &part
->mp_next
;
1187 if (!(p
= get_content (fp
, ct
->c_file
,
1188 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1196 fseek (fp
, pos
, SEEK_SET
);
1199 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1203 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1204 if (p
->c_end
< p
->c_begin
)
1205 p
->c_begin
= p
->c_end
;
1210 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1216 if (! suppress_bogus_mp_content_warning
) {
1217 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1219 bogus_mp_content
= 1;
1221 if (!inout
&& part
) {
1223 p
->c_end
= ct
->c_end
;
1225 if (p
->c_begin
>= p
->c_end
) {
1226 for (next
= &m
->mp_parts
; *next
!= part
;
1227 next
= &((*next
)->mp_next
))
1231 free ((char *) part
);
1236 /* reverse the order of the parts for multipart/alternative */
1237 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1243 * label all subparts with part number, and
1244 * then initialize the content of the subpart.
1249 char partnam
[BUFSIZ
];
1252 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1253 pp
= partnam
+ strlen (partnam
);
1258 for (part
= m
->mp_parts
, partnum
= 1; part
;
1259 part
= part
->mp_next
, partnum
++) {
1262 sprintf (pp
, "%d", partnum
);
1263 p
->c_partno
= add (partnam
, NULL
);
1265 /* initialize the content of the subparts */
1266 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1275 get_leftover_mp_content (ct
, 1);
1276 get_leftover_mp_content (ct
, 0);
1286 * reverse the order of the parts of a multipart/alternative,
1287 * presumably to put the "most favored" alternative first, for
1288 * ease of choosing/displaying it later on. from a mail message on
1289 * nmh-workers, from kenh:
1290 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1291 * see code in mhn that did the same thing... Acccording to the RCS
1292 * logs, that code was around from the initial checkin of mhn.c by
1293 * John Romine in 1992, which is as far back as we have."
1296 reverse_parts (CT ct
)
1298 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1302 /* Reverse the order of its parts by walking the mp_parts list
1303 and pushing each node to the front. */
1304 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1305 next
= part
->mp_next
;
1306 part
->mp_next
= m
->mp_parts
;
1312 move_preferred_part (CT ct
, char *type
, char *subtype
)
1314 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1315 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1319 /* move the matching part(s) to the head of the list: walk the
1320 * list of parts, move matching parts to a new list (maintaining
1321 * their order), and finally, concatenate the old list onto the
1328 head
->mp_next
= m
->mp_parts
;
1329 nhead
->mp_next
= NULL
;
1333 part
= head
->mp_next
;
1334 while (part
!= NULL
) {
1335 ci
= &part
->mp_part
->c_ctinfo
;
1336 if (!strcasecmp(ci
->ci_type
, type
) &&
1337 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1338 prev
->mp_next
= part
->mp_next
;
1339 part
->mp_next
= NULL
;
1340 ntail
->mp_next
= part
;
1342 part
= prev
->mp_next
;
1345 part
= prev
->mp_next
;
1348 ntail
->mp_next
= head
->mp_next
;
1349 m
->mp_parts
= nhead
->mp_next
;
1354 * move parts that match the user's preferences (-prefer) to the head
1355 * of the line. process preferences in reverse so first one given
1356 * ends up first in line
1362 for (i
= npreferred
-1; i
>= 0; i
--)
1363 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1368 /* parse_mime() arranges alternates in reverse (priority) order. This
1369 function can be used to reverse them back. This will put, for
1370 example, a text/plain part before a text/html part in a
1371 multipart/alternative part, for example, where it belongs. */
1373 reverse_alternative_parts (CT ct
) {
1374 if (ct
->c_type
== CT_MULTIPART
) {
1375 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1378 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1382 /* And call recursively on each part of a multipart. */
1383 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1384 reverse_alternative_parts (part
->mp_part
);
1397 CI ci
= &ct
->c_ctinfo
;
1399 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1401 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1402 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1406 /* check for missing subtype */
1407 if (!*ci
->ci_subtype
)
1408 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1411 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1413 switch (ct
->c_subtype
) {
1414 case MESSAGE_RFC822
:
1417 case MESSAGE_PARTIAL
:
1422 if ((p
= (struct partial
*) mh_xcalloc (1, sizeof(*p
))) == NULL
)
1423 adios (NULL
, "out of memory");
1424 ct
->c_ctparams
= (void *) p
;
1426 /* scan for parameters "id", "number", and "total" */
1427 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1428 if (!strcasecmp (pm
->pm_name
, "id")) {
1429 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1432 if (!strcasecmp (pm
->pm_name
, "number")) {
1433 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1434 || p
->pm_partno
< 1) {
1437 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1438 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1439 ct
->c_file
, TYPE_FIELD
);
1444 if (!strcasecmp (pm
->pm_name
, "total")) {
1445 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1454 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1456 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1457 ci
->ci_type
, ci
->ci_subtype
,
1458 ct
->c_file
, TYPE_FIELD
);
1464 case MESSAGE_EXTERNAL
:
1471 if ((e
= (struct exbody
*) mh_xcalloc (1, sizeof(*e
))) == NULL
)
1472 adios (NULL
, "out of memory");
1473 ct
->c_ctparams
= (void *) e
;
1476 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1477 advise (ct
->c_file
, "unable to open for reading");
1481 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1483 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1491 p
->c_ceopenfnx
= NULL
;
1492 if ((exresult
= params_external (ct
, 0)) != NOTOK
1493 && p
->c_ceopenfnx
== openMail
) {
1497 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1499 content_error (NULL
, ct
,
1500 "empty body for access-type=mail-server");
1504 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1505 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1507 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1509 adios ("failed", "fread");
1512 adios (NULL
, "unexpected EOF from fread");
1515 bp
+= cc
, size
-= cc
;
1522 p
->c_end
= p
->c_begin
;
1527 if (exresult
== NOTOK
)
1529 if (e
->eb_flags
== NOTOK
)
1532 switch (p
->c_type
) {
1537 if (p
->c_subtype
!= MESSAGE_RFC822
)
1541 e
->eb_partno
= ct
->c_partno
;
1543 (*p
->c_ctinitfnx
) (p
);
1558 params_external (CT ct
, int composing
)
1561 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1562 CI ci
= &ct
->c_ctinfo
;
1564 ct
->c_ceopenfnx
= NULL
;
1565 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1566 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1567 struct str2init
*s2i
;
1568 CT p
= e
->eb_content
;
1570 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1571 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1574 e
->eb_access
= pm
->pm_value
;
1575 e
->eb_flags
= NOTOK
;
1576 p
->c_encoding
= CE_EXTERNAL
;
1579 e
->eb_access
= s2i
->si_key
;
1580 e
->eb_flags
= s2i
->si_val
;
1581 p
->c_encoding
= CE_EXTERNAL
;
1583 /* Call the Init function for this external type */
1584 if ((*s2i
->si_init
)(p
) == NOTOK
)
1588 if (!strcasecmp (pm
->pm_name
, "name")) {
1589 e
->eb_name
= pm
->pm_value
;
1592 if (!strcasecmp (pm
->pm_name
, "permission")) {
1593 e
->eb_permission
= pm
->pm_value
;
1596 if (!strcasecmp (pm
->pm_name
, "site")) {
1597 e
->eb_site
= pm
->pm_value
;
1600 if (!strcasecmp (pm
->pm_name
, "directory")) {
1601 e
->eb_dir
= pm
->pm_value
;
1604 if (!strcasecmp (pm
->pm_name
, "mode")) {
1605 e
->eb_mode
= pm
->pm_value
;
1608 if (!strcasecmp (pm
->pm_name
, "size")) {
1609 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1612 if (!strcasecmp (pm
->pm_name
, "server")) {
1613 e
->eb_server
= pm
->pm_value
;
1616 if (!strcasecmp (pm
->pm_name
, "subject")) {
1617 e
->eb_subject
= pm
->pm_value
;
1620 if (!strcasecmp (pm
->pm_name
, "url")) {
1622 * According to RFC 2017, we have to remove all whitespace from
1626 char *u
, *p
= pm
->pm_value
;
1627 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1629 for (; *p
!= '\0'; p
++) {
1630 if (! isspace((unsigned char) *p
))
1637 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1638 e
->eb_body
= getcpy (pm
->pm_value
);
1643 if (!e
->eb_access
) {
1645 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1646 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1659 InitApplication (CT ct
)
1661 CI ci
= &ct
->c_ctinfo
;
1664 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1671 * TRANSFER ENCODINGS
1675 init_encoding (CT ct
, OpenCEFunc openfnx
)
1677 ct
->c_ceopenfnx
= openfnx
;
1678 ct
->c_ceclosefnx
= close_encoding
;
1679 ct
->c_cesizefnx
= size_encoding
;
1686 close_encoding (CT ct
)
1688 CE ce
= &ct
->c_cefile
;
1697 static unsigned long
1698 size_encoding (CT ct
)
1703 CE ce
= &ct
->c_cefile
;
1706 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1707 return (long) st
.st_size
;
1710 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1711 return (long) st
.st_size
;
1716 if (ct
->c_encoding
== CE_EXTERNAL
)
1717 return (ct
->c_end
- ct
->c_begin
);
1720 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1721 return (ct
->c_end
- ct
->c_begin
);
1723 if (fstat (fd
, &st
) != NOTOK
)
1724 size
= (long) st
.st_size
;
1728 (*ct
->c_ceclosefnx
) (ct
);
1740 return init_encoding (ct
, openBase64
);
1745 openBase64 (CT ct
, char **file
)
1748 int fd
, own_ct_fp
= 0;
1749 char *cp
, *buffer
= NULL
;
1750 /* sbeck -- handle suffixes */
1752 CE ce
= &ct
->c_cefile
;
1753 unsigned char *decoded
;
1755 unsigned char digest
[16];
1758 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1763 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1764 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1770 if (*file
== NULL
) {
1773 ce
->ce_file
= add (*file
, NULL
);
1777 /* sbeck@cise.ufl.edu -- handle suffixes */
1779 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1780 if (ce
->ce_unlink
) {
1781 /* Create temporary file with filename extension. */
1782 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1783 adios(NULL
, "unable to create temporary file in %s",
1787 ce
->ce_file
= add (cp
, ce
->ce_file
);
1789 } else if (*file
== NULL
) {
1791 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1792 adios(NULL
, "unable to create temporary file in %s",
1795 ce
->ce_file
= add (tempfile
, NULL
);
1798 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1799 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1803 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1804 adios (NULL
, "internal error(1)");
1806 buffer
= mh_xmalloc (len
+ 1);
1809 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1810 content_error (ct
->c_file
, ct
, "unable to open for reading");
1816 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1819 switch (cc
= read (fd
, cp
, len
)) {
1821 content_error (ct
->c_file
, ct
, "error reading from");
1825 content_error (NULL
, ct
, "premature eof");
1836 /* decodeBase64() requires null-terminated input. */
1839 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1840 ct
->c_digested
? digest
: NULL
) == OK
) {
1842 unsigned char *decoded_p
= decoded
;
1843 for (i
= 0; i
< decoded_len
; ++i
) {
1844 putc (*decoded_p
++, ce
->ce_fp
);
1846 free ((char *) decoded
);
1847 if (ferror (ce
->ce_fp
)) {
1848 content_error (ce
->ce_file
, ct
, "error writing to");
1852 if (ct
->c_digested
) {
1853 if (memcmp(digest
, ct
->c_digest
,
1854 sizeof(digest
) / sizeof(digest
[0]))) {
1855 content_error (NULL
, ct
,
1856 "content integrity suspect (digest mismatch) -- continuing");
1859 fprintf (stderr
, "content integrity confirmed\n");
1867 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1869 if (fflush (ce
->ce_fp
)) {
1870 content_error (ce
->ce_file
, ct
, "error writing to");
1874 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1877 *file
= ce
->ce_file
;
1883 return fileno (ce
->ce_fp
);
1890 free_encoding (ct
, 0);
1900 static char hex2nib
[0x80] = {
1901 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1908 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1909 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1910 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1911 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1913 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1914 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1915 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1916 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1923 return init_encoding (ct
, openQuoted
);
1928 openQuoted (CT ct
, char **file
)
1930 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1936 CE ce
= &ct
->c_cefile
;
1937 /* sbeck -- handle suffixes */
1942 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1947 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1948 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1954 if (*file
== NULL
) {
1957 ce
->ce_file
= add (*file
, NULL
);
1961 /* sbeck@cise.ufl.edu -- handle suffixes */
1963 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1964 if (ce
->ce_unlink
) {
1965 /* Create temporary file with filename extension. */
1966 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1967 adios(NULL
, "unable to create temporary file in %s",
1971 ce
->ce_file
= add (cp
, ce
->ce_file
);
1973 } else if (*file
== NULL
) {
1975 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1976 adios(NULL
, "unable to create temporary file in %s",
1979 ce
->ce_file
= add (tempfile
, NULL
);
1982 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1983 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1987 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1988 adios (NULL
, "internal error(2)");
1991 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1992 content_error (ct
->c_file
, ct
, "unable to open for reading");
1998 if ((digested
= ct
->c_digested
))
1999 MD5Init (&mdContext
);
2006 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2008 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2009 content_error (NULL
, ct
, "premature eof");
2013 if ((cc
= gotlen
) > len
)
2017 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2018 if (!isspace ((unsigned char) *ep
))
2022 for (; cp
< ep
; cp
++) {
2024 /* in an escape sequence */
2026 /* at byte 1 of an escape sequence */
2027 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2028 /* next is byte 2 */
2031 /* at byte 2 of an escape sequence */
2033 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2034 putc (mask
, ce
->ce_fp
);
2036 MD5Update (&mdContext
, &mask
, 1);
2037 if (ferror (ce
->ce_fp
)) {
2038 content_error (ce
->ce_file
, ct
, "error writing to");
2041 /* finished escape sequence; next may be literal or a new
2042 * escape sequence */
2045 /* on to next byte */
2049 /* not in an escape sequence */
2051 /* starting an escape sequence, or invalid '='? */
2052 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2053 /* "=\n" soft line break, eat the \n */
2057 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2058 /* We don't have 2 bytes left, so this is an invalid
2059 * escape sequence; just show the raw bytes (below). */
2060 } else if (isxdigit ((unsigned char) cp
[1]) &&
2061 isxdigit ((unsigned char) cp
[2])) {
2062 /* Next 2 bytes are hex digits, making this a valid escape
2063 * sequence; let's decode it (above). */
2067 /* One or both of the next 2 is out of range, making this
2068 * an invalid escape sequence; just show the raw bytes
2073 /* Just show the raw byte. */
2074 putc (*cp
, ce
->ce_fp
);
2077 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2079 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2082 if (ferror (ce
->ce_fp
)) {
2083 content_error (ce
->ce_file
, ct
, "error writing to");
2089 content_error (NULL
, ct
,
2090 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2094 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2096 if (fflush (ce
->ce_fp
)) {
2097 content_error (ce
->ce_file
, ct
, "error writing to");
2102 unsigned char digest
[16];
2104 MD5Final (digest
, &mdContext
);
2105 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2106 sizeof(digest
) / sizeof(digest
[0])))
2107 content_error (NULL
, ct
,
2108 "content integrity suspect (digest mismatch) -- continuing");
2111 fprintf (stderr
, "content integrity confirmed\n");
2114 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2117 *file
= ce
->ce_file
;
2123 return fileno (ce
->ce_fp
);
2126 free_encoding (ct
, 0);
2143 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2146 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2152 open7Bit (CT ct
, char **file
)
2154 int cc
, fd
, len
, own_ct_fp
= 0;
2155 char buffer
[BUFSIZ
];
2156 /* sbeck -- handle suffixes */
2159 CE ce
= &ct
->c_cefile
;
2162 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2167 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2168 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2174 if (*file
== NULL
) {
2177 ce
->ce_file
= add (*file
, NULL
);
2181 /* sbeck@cise.ufl.edu -- handle suffixes */
2183 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2184 if (ce
->ce_unlink
) {
2185 /* Create temporary file with filename extension. */
2186 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2187 adios(NULL
, "unable to create temporary file in %s",
2191 ce
->ce_file
= add (cp
, ce
->ce_file
);
2193 } else if (*file
== NULL
) {
2195 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2196 adios(NULL
, "unable to create temporary file in %s",
2199 ce
->ce_file
= add (tempfile
, NULL
);
2202 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2203 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2207 if (ct
->c_type
== CT_MULTIPART
) {
2208 CI ci
= &ct
->c_ctinfo
;
2212 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2213 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2214 + 1 + strlen (ci
->ci_subtype
);
2215 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2218 fputs (buffer
, ce
->ce_fp
);
2222 if (ci
->ci_comment
) {
2223 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2224 fputs ("\n\t", ce
->ce_fp
);
2228 putc (' ', ce
->ce_fp
);
2231 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2234 fprintf (ce
->ce_fp
, "\n");
2236 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2238 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2240 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2241 fprintf (ce
->ce_fp
, "\n");
2244 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2245 adios (NULL
, "internal error(3)");
2248 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2249 content_error (ct
->c_file
, ct
, "unable to open for reading");
2255 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2257 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2259 content_error (ct
->c_file
, ct
, "error reading from");
2263 content_error (NULL
, ct
, "premature eof");
2271 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2272 advise ("open7Bit", "fwrite");
2274 if (ferror (ce
->ce_fp
)) {
2275 content_error (ce
->ce_file
, ct
, "error writing to");
2280 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2282 if (fflush (ce
->ce_fp
)) {
2283 content_error (ce
->ce_file
, ct
, "error writing to");
2287 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2290 *file
= ce
->ce_file
;
2295 return fileno (ce
->ce_fp
);
2298 free_encoding (ct
, 0);
2312 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2314 char cachefile
[BUFSIZ
];
2317 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2322 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2323 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2329 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2330 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2331 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2332 ce
->ce_file
= getcpy (cachefile
);
2336 admonish (cachefile
, "unable to fopen for reading");
2340 *fd
= fileno (ce
->ce_fp
);
2344 *file
= ce
->ce_file
;
2345 *fd
= fileno (ce
->ce_fp
);
2356 return init_encoding (ct
, openFile
);
2361 openFile (CT ct
, char **file
)
2364 char cachefile
[BUFSIZ
];
2365 struct exbody
*e
= ct
->c_ctexbody
;
2366 CE ce
= &ct
->c_cefile
;
2368 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2380 content_error (NULL
, ct
, "missing name parameter");
2384 ce
->ce_file
= getcpy (e
->eb_name
);
2387 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2388 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2392 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2393 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2394 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2398 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2399 if ((fp
= fopen (cachefile
, "w"))) {
2401 char buffer
[BUFSIZ
];
2402 FILE *gp
= ce
->ce_fp
;
2404 fseek (gp
, 0L, SEEK_SET
);
2406 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2408 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2409 advise ("openFile", "fwrite");
2414 admonish (ce
->ce_file
, "error reading");
2415 (void) m_unlink (cachefile
);
2419 admonish (cachefile
, "error writing");
2420 (void) m_unlink (cachefile
);
2427 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2428 *file
= ce
->ce_file
;
2429 return fileno (ce
->ce_fp
);
2439 return init_encoding (ct
, openFTP
);
2444 openFTP (CT ct
, char **file
)
2446 int cachetype
, caching
, fd
;
2448 char *bp
, *ftp
, *user
, *pass
;
2449 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2451 CE ce
= &ct
->c_cefile
;
2452 static char *username
= NULL
;
2453 static char *password
= NULL
;
2457 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2463 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2474 if (!e
->eb_name
|| !e
->eb_site
) {
2475 content_error (NULL
, ct
, "missing %s parameter",
2476 e
->eb_name
? "site": "name");
2480 /* Get the buffer ready to go */
2482 buflen
= sizeof(buffer
);
2485 * Construct the query message for user
2487 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2493 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2499 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2500 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2505 if (e
->eb_size
> 0) {
2506 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2511 snprintf (bp
, buflen
, "? ");
2514 * Now, check the answer
2516 if (!read_yes_or_no_if_tty (buffer
))
2521 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2525 ruserpass (e
->eb_site
, &username
, &password
);
2530 ce
->ce_unlink
= (*file
== NULL
);
2532 cachefile
[0] = '\0';
2533 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2534 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2535 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2536 if (*file
== NULL
) {
2543 ce
->ce_file
= add (*file
, NULL
);
2545 ce
->ce_file
= add (cachefile
, NULL
);
2548 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2549 adios(NULL
, "unable to create temporary file in %s",
2552 ce
->ce_file
= add (tempfile
, NULL
);
2555 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2556 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2561 int child_id
, i
, vecp
;
2565 vec
[vecp
++] = r1bindex (ftp
, '/');
2566 vec
[vecp
++] = e
->eb_site
;
2569 vec
[vecp
++] = e
->eb_dir
;
2570 vec
[vecp
++] = e
->eb_name
;
2571 vec
[vecp
++] = ce
->ce_file
,
2572 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2573 ? "ascii" : "binary";
2578 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2582 adios ("fork", "unable to");
2586 close (fileno (ce
->ce_fp
));
2588 fprintf (stderr
, "unable to exec ");
2594 if (pidXwait (child_id
, NULL
)) {
2595 username
= password
= NULL
;
2605 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2610 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2611 if ((fp
= fopen (cachefile
, "w"))) {
2613 FILE *gp
= ce
->ce_fp
;
2615 fseek (gp
, 0L, SEEK_SET
);
2617 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2619 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2620 advise ("openFTP", "fwrite");
2625 admonish (ce
->ce_file
, "error reading");
2626 (void) m_unlink (cachefile
);
2630 admonish (cachefile
, "error writing");
2631 (void) m_unlink (cachefile
);
2639 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2640 *file
= ce
->ce_file
;
2641 return fileno (ce
->ce_fp
);
2652 return init_encoding (ct
, openMail
);
2657 openMail (CT ct
, char **file
)
2659 int child_id
, fd
, i
, vecp
;
2661 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2662 struct exbody
*e
= ct
->c_ctexbody
;
2663 CE ce
= &ct
->c_cefile
;
2665 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2676 if (!e
->eb_server
) {
2677 content_error (NULL
, ct
, "missing server parameter");
2681 /* Get buffer ready to go */
2683 buflen
= sizeof(buffer
);
2685 /* Now, construct query message */
2686 snprintf (bp
, buflen
, "Retrieve content");
2692 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2698 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2700 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2702 /* Now, check answer */
2703 if (!read_yes_or_no_if_tty (buffer
))
2707 vec
[vecp
++] = r1bindex (mailproc
, '/');
2708 vec
[vecp
++] = e
->eb_server
;
2709 vec
[vecp
++] = "-subject";
2710 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2711 vec
[vecp
++] = "-body";
2712 vec
[vecp
++] = e
->eb_body
;
2715 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2719 advise ("fork", "unable to");
2723 execvp (mailproc
, vec
);
2724 fprintf (stderr
, "unable to exec ");
2730 if (pidXwait (child_id
, NULL
) == OK
)
2731 advise (NULL
, "request sent");
2735 if (*file
== NULL
) {
2737 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2738 adios(NULL
, "unable to create temporary file in %s",
2741 ce
->ce_file
= add (tempfile
, NULL
);
2744 ce
->ce_file
= add (*file
, NULL
);
2748 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2749 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2753 /* showproc is for mhshow and mhstore, though mhlist -debug
2754 * prints it, too. */
2756 free (ct
->c_showproc
);
2757 ct
->c_showproc
= add ("true", NULL
);
2759 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2760 *file
= ce
->ce_file
;
2761 return fileno (ce
->ce_fp
);
2772 return init_encoding (ct
, openURL
);
2777 openURL (CT ct
, char **file
)
2779 struct exbody
*e
= ct
->c_ctexbody
;
2780 CE ce
= &ct
->c_cefile
;
2781 char *urlprog
, *program
;
2782 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2783 int fd
, caching
, cachetype
;
2784 struct msgs_array args
= { 0, 0, NULL
};
2787 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2791 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2795 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2807 content_error(NULL
, ct
, "missing url parameter");
2811 ce
->ce_unlink
= (*file
== NULL
);
2813 cachefile
[0] = '\0';
2815 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2816 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2817 if (*file
== NULL
) {
2824 ce
->ce_file
= add(*file
, NULL
);
2826 ce
->ce_file
= add(cachefile
, NULL
);
2829 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2830 adios(NULL
, "unable to create temporary file in %s",
2833 ce
->ce_file
= add (tempfile
, NULL
);
2836 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2837 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2841 switch (child_id
= fork()) {
2843 adios ("fork", "unable to");
2847 argsplit_msgarg(&args
, urlprog
, &program
);
2848 app_msgarg(&args
, e
->eb_url
);
2849 app_msgarg(&args
, NULL
);
2850 dup2(fileno(ce
->ce_fp
), 1);
2851 close(fileno(ce
->ce_fp
));
2852 execvp(program
, args
.msgs
);
2853 fprintf(stderr
, "Unable to exec ");
2859 if (pidXwait(child_id
, NULL
)) {
2867 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2872 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2873 if ((fp
= fopen(cachefile
, "w"))) {
2875 FILE *gp
= ce
->ce_fp
;
2877 fseeko(gp
, 0, SEEK_SET
);
2879 while ((cc
= fread(buffer
, sizeof(*buffer
),
2880 sizeof(buffer
), gp
)) > 0)
2881 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2882 advise ("openURL", "fwrite");
2888 admonish(ce
->ce_file
, "error reading");
2889 (void) m_unlink (cachefile
);
2896 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2897 *file
= ce
->ce_file
;
2903 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2904 * has to be base64 decoded.
2907 readDigest (CT ct
, char *cp
)
2909 unsigned char *digest
;
2912 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2913 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2915 if (strlen ((char *) digest
) <= maxlen
) {
2916 memcpy (ct
->c_digest
, digest
, maxlen
);
2921 fprintf (stderr
, "MD5 digest=");
2922 for (i
= 0; i
< maxlen
; ++i
) {
2923 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2925 fprintf (stderr
, "\n");
2931 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2932 (int) strlen ((char *) digest
));
2943 /* Multipart parts might have content before the first subpart and/or
2944 after the last subpart that hasn't been stored anywhere else, so do
2947 get_leftover_mp_content (CT ct
, int before
/* or after */)
2949 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2951 int found_boundary
= 0;
2957 char *content
= NULL
;
2959 if (! m
) return NOTOK
;
2962 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2964 /* Isolate the beginning of this part to the beginning of the
2965 first subpart and save any content between them. */
2966 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2967 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2968 boundary
= concat ("--", m
->mp_start
, NULL
);
2970 struct part
*last_subpart
= NULL
;
2971 struct part
*subpart
;
2973 /* Go to the last subpart to get its end position. */
2974 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2975 last_subpart
= subpart
;
2978 if (last_subpart
== NULL
) return NOTOK
;
2980 /* Isolate the end of the last subpart to the end of this part
2981 and save any content between them. */
2982 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2983 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2984 boundary
= concat ("--", m
->mp_stop
, NULL
);
2987 /* Back up by 1 to pick up the newline. */
2988 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2990 /* Don't look beyond beginning of first subpart (before) or
2991 next part (after). */
2992 if (read
> max
) bufp
[read
-max
] = '\0';
2995 if (! strcmp (bufp
, boundary
)) {
2999 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3005 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3007 char *old_content
= content
;
3008 content
= concat (content
, bufp
, NULL
);
3012 ? concat ("\n", bufp
, NULL
)
3013 : concat (bufp
, NULL
);
3018 if (found_boundary
|| read
> max
) break;
3020 if (read
> max
) break;
3024 /* Skip the newline if that's all there is. */
3028 /* Remove trailing newline, except at EOF. */
3029 if ((before
|| ! feof (ct
->c_fp
)) &&
3030 (cp
= content
+ strlen (content
)) > content
&&
3035 if (strlen (content
) > 1) {
3037 m
->mp_content_before
= content
;
3039 m
->mp_content_after
= content
;
3054 ct_type_str (int type
) {
3056 case CT_APPLICATION
:
3057 return "application";
3073 return "unknown_type";
3079 ct_subtype_str (int type
, int subtype
) {
3081 case CT_APPLICATION
:
3083 case APPLICATION_OCTETS
:
3085 case APPLICATION_POSTSCRIPT
:
3086 return "postscript";
3088 return "unknown_app_subtype";
3092 case MESSAGE_RFC822
:
3094 case MESSAGE_PARTIAL
:
3096 case MESSAGE_EXTERNAL
:
3099 return "unknown_msg_subtype";
3105 case MULTI_ALTERNATE
:
3106 return "alternative";
3109 case MULTI_PARALLEL
:
3114 return "unknown_multipart_subtype";
3125 return "unknown_text_subtype";
3128 return "unknown_type";
3134 ct_str_type (const char *type
) {
3135 struct str2init
*s2i
;
3137 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3138 if (! strcasecmp (type
, s2i
->si_key
)) {
3142 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3151 ct_str_subtype (int type
, const char *subtype
) {
3155 case CT_APPLICATION
:
3156 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3157 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3161 return kv
->kv_value
;
3163 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3164 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3168 return kv
->kv_value
;
3170 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3171 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3175 return kv
->kv_value
;
3177 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3178 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3182 return kv
->kv_value
;
3189 /* Find the content type and InitFunc for the CT. */
3190 const struct str2init
*
3191 get_ct_init (int type
) {
3192 const struct str2init
*sp
;
3194 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3195 if (type
== sp
->si_val
) {
3204 ce_str (int encoding
) {
3209 return "quoted-printable";
3225 /* Find the content type and InitFunc for the content encoding method. */
3226 const struct str2init
*
3227 get_ce_method (const char *method
) {
3228 struct str2init
*sp
;
3230 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3231 if (! strcasecmp (method
, sp
->si_key
)) {
3240 * Parse a series of MIME attributes (or parameters) given a header as
3243 * Arguments include:
3245 * filename - Name of input file (for error messages)
3246 * fieldname - Name of field being processed
3247 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3248 * Updated to point to end of attributes when finished.
3249 * param_head - Pointer to head of parameter list
3250 * param_tail - Pointer to tail of parameter list
3251 * commentp - Pointer to header comment pointer (may be NULL)
3253 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3254 * DONE to indicate a benign error (minor parsing error, but the program
3259 parse_header_attrs (const char *filename
, const char *fieldname
,
3260 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3263 char *cp
= *header_attrp
;
3269 struct sectlist
*next
;
3275 struct sectlist
*sechead
;
3276 struct parmlist
*next
;
3277 } *pp
, *pp2
, *phead
= NULL
;
3279 while (*cp
== ';') {
3280 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3281 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3284 while (isspace ((unsigned char) *cp
))
3288 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3293 if (! suppress_extraneous_trailing_semicolon_warning
) {
3295 "extraneous trailing ';' in message %s's %s: "
3297 filename
, fieldname
);
3299 extraneous_trailing_semicolon
= 1;
3303 /* down case the attribute name */
3304 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3305 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
3306 *dp
= tolower ((unsigned char) *dp
);
3308 for (up
= dp
; isspace ((unsigned char) *dp
);)
3310 if (dp
== cp
|| *dp
!= '=') {
3312 "invalid parameter in message %s's %s: "
3313 "field\n%*sparameter %s (error detected at offset %d)",
3314 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3319 * To handle RFC 2231, we have to deal with the following extensions:
3321 * name*=encoded-value
3322 * name*<N>=part-N-of-a-parameter-value
3323 * name*<N>*=encoded-part-N-of-a-parameter-value
3326 * If there's a * right before the equal sign, it's encoded.
3327 * If there's a * and one or more digits, then it's section N.
3329 * Remember we can have one or the other, or both. cp points to
3330 * beginning of name, up points past the last character in the
3334 for (vp
= cp
; vp
< up
; vp
++) {
3335 if (*vp
== '*' && vp
< up
- 1) {
3338 } else if (*vp
== '*' && vp
== up
- 1) {
3340 } else if (partial
) {
3341 if (isdigit((unsigned char) *vp
))
3342 index
= *vp
- '0' + index
* 10;
3344 advise (NULL
, "invalid parameter index in message %s's "
3345 "%s: field\n%*s(parameter %s)", filename
,
3346 fieldname
, strlen(invo_name
) + 2, "", cp
);
3355 * Break out the parameter name and value sections and allocate
3359 nameptr
= mh_xmalloc(len
+ 1);
3360 strncpy(nameptr
, cp
, len
);
3361 nameptr
[len
] = '\0';
3363 for (dp
++; isspace ((unsigned char) *dp
);)
3368 * Single quotes delimit the character set and language tag.
3369 * They are required on the first section (or a complete
3374 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3380 charset
= mh_xmalloc(len
+ 1);
3381 strncpy(charset
, dp
, len
);
3382 charset
[len
] = '\0';
3388 advise(NULL
, "missing charset in message %s's %s: "
3389 "field\n%*s(parameter %s)", filename
, fieldname
,
3390 strlen(invo_name
) + 2, "", nameptr
);
3396 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3403 lang
= mh_xmalloc(len
+ 1);
3404 strncpy(lang
, dp
, len
);
3411 advise(NULL
, "missing language tag in message %s's %s: "
3412 "field\n%*s(parameter %s)", filename
, fieldname
,
3413 strlen(invo_name
) + 2, "", nameptr
);
3424 * At this point vp should be pointing at the beginning
3425 * of the encoded value/section. Continue until we reach
3426 * the end or get whitespace. But first, calculate the
3427 * length so we can allocate the correct buffer size.
3430 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3432 if (*(vp
+ 1) == '\0' ||
3433 !isxdigit((unsigned char) *(vp
+ 1)) ||
3434 *(vp
+ 2) == '\0' ||
3435 !isxdigit((unsigned char) *(vp
+ 2))) {
3436 advise(NULL
, "invalid encoded sequence in message "
3437 "%s's %s: field\n%*s(parameter %s)",
3438 filename
, fieldname
, strlen(invo_name
) + 2,
3452 up
= valptr
= mh_xmalloc(len
+ 1);
3454 for (vp
= dp
; istoken(*vp
); vp
++) {
3456 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3467 * A "normal" string. If it's got a leading quote, then we
3468 * strip the quotes out. Otherwise go until we reach the end
3469 * or get whitespace. Note we scan it twice; once to get the
3470 * length, then the second time copies it into the destination
3477 for (cp
= dp
+ 1;;) {
3482 "invalid quoted-string in message %s's %s: "
3483 "field\n%*s(parameter %s)",
3484 filename
, fieldname
, strlen(invo_name
) + 2, "",
3507 for (cp
= dp
; istoken (*cp
); cp
++) {
3512 valptr
= mh_xmalloc(len
+ 1);
3516 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3524 strncpy(valptr
, cp
= dp
, len
);
3532 * If 'partial' is set, we don't allocate a parameter now. We
3533 * put it on the parameter linked list to be reassembled later.
3535 * "phead" points to a list of all parameters we need to reassemble.
3536 * Each parameter has a list of sections. We insert the sections in
3541 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3542 if (strcasecmp(nameptr
, pp
->name
) == 0)
3547 pp
= mh_xmalloc(sizeof(*pp
));
3548 memset(pp
, 0, sizeof(*pp
));
3555 * Insert this into the section linked list
3558 sp
= mh_xmalloc(sizeof(*sp
));
3559 memset(sp
, 0, sizeof(*sp
));
3564 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3565 sp
->next
= pp
->sechead
;
3568 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3569 if (sp2
->index
== sp
->index
) {
3570 advise (NULL
, "duplicate index (%d) in message "
3571 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3572 filename
, fieldname
, strlen(invo_name
) + 2, "",
3577 if (sp2
->index
< sp
->index
&&
3578 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3579 sp
->next
= sp2
->next
;
3586 advise(NULL
, "Internal error: cannot insert partial "
3587 "param in message %s's %s: field\n%*s(parameter %s)",
3588 filename
, fieldname
, strlen(invo_name
) + 2, "",
3596 * Save our charset and lang tags.
3599 if (index
== 0 && encoded
) {
3602 pp
->charset
= charset
;
3608 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3609 pm
->pm_charset
= charset
;
3613 while (isspace ((unsigned char) *cp
))
3617 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3623 * Now that we're done, reassemble all of the partial parameters.
3626 for (pp
= phead
; pp
!= NULL
; ) {
3630 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3631 if (sp
->index
!= pindex
++) {
3632 advise(NULL
, "missing section %d for parameter in "
3633 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3634 filename
, fieldname
, strlen(invo_name
) + 2, "",
3641 p
= q
= mh_xmalloc(tlen
+ 1);
3642 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3643 memcpy(q
, sp
->value
, sp
->len
);
3653 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3654 pm
->pm_charset
= pp
->charset
;
3655 pm
->pm_lang
= pp
->lang
;
3666 * Return the charset for a particular content type.
3670 content_charset (CT ct
) {
3671 char *ret_charset
= NULL
;
3673 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3675 return ret_charset
? ret_charset
: getcpy ("US-ASCII");
3680 * Create a string based on a list of output parameters. Assume that this
3681 * parameter string will be appended to an existing header, so start out
3682 * with the separator (;). Perform RFC 2231 encoding when necessary.
3686 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3688 char *paramout
= NULL
;
3689 char line
[CPERLIN
* 2], *q
;
3690 int curlen
, index
, cont
, encode
, i
;
3691 size_t valoff
, numchars
;
3693 while (params
!= NULL
) {
3699 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3702 if (strlen(params
->pm_name
) > CPERLIN
) {
3703 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3709 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3712 * Loop until we get a parameter that fits within a line. We
3713 * assume new lines start with a tab, so check our overflow based
3723 * At this point we're definitely continuing the line, so
3724 * be sure to include the parameter name and section index.
3727 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3728 params
->pm_name
, index
);
3731 * Both of these functions do a NUL termination
3735 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3736 numchars
, valoff
, index
);
3738 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3749 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3754 * "line" starts with a ;\n\t, so that doesn't count against
3755 * the length. But add 8 since it starts with a tab; that's
3756 * how we end up with 5.
3759 initialwidth
= strlen(line
) + 5;
3762 * At this point the line should be built, so add it to our
3763 * current output buffer.
3766 paramout
= add(line
, paramout
);
3770 * If this won't fit on the line, start a new one. Save room in
3771 * case we need a semicolon on the end
3774 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3786 * At this point, we're either finishing a contined parameter, or
3787 * we're working on a new one.
3791 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3792 params
->pm_name
, index
);
3794 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3799 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3800 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3802 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3803 strlen(params
->pm_value
+ valoff
), valoff
);
3811 paramout
= add(line
, paramout
);
3812 initialwidth
+= strlen(line
);
3814 params
= params
->pm_next
;
3818 *offsetout
= initialwidth
;
3824 * Calculate the size of a parameter.
3828 * pm - The parameter being output
3829 * index - If continuing the parameter, the index of the section
3831 * valueoff - The current offset into the parameter value that we're
3832 * working on (previous sections have consumed valueoff bytes).
3833 * encode - Set if we should perform encoding on this parameter section
3834 * (given that we're consuming bytesfit bytes).
3835 * cont - Set if the remaining data in value will not fit on a single
3836 * line and will need to be continued.
3837 * bytesfit - The number of bytes that we can consume from the parameter
3838 * value and still fit on a completely new line. The
3839 * calculation assumes the new line starts with a tab,
3840 * includes the parameter name and any encoding, and fits
3841 * within CPERLIN bytes. Will always be at least 1.
3845 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3848 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3849 size_t len
= 0, fit
= 0;
3850 int fitlimit
= 0, eightbit
, maxfit
;
3855 * Add up the length. First, start with the parameter name.
3858 len
= strlen(pm
->pm_name
);
3861 * Scan the parameter value and see if we need to do encoding for this
3865 eightbit
= contains8bit(start
, NULL
);
3868 * Determine if we need to encode this section. Encoding is necessary if:
3870 * - There are any 8-bit characters at all and we're on the first
3872 * - There are 8-bit characters within N bytes of our section start.
3873 * N is calculated based on the number of bytes it would take to
3874 * reach CPERLIN. Specifically:
3875 * 8 (starting tab) +
3876 * strlen(param name) +
3877 * 4 ('* for section marker, '=', opening/closing '"')
3879 * is the number of bytes used by everything that isn't part of the
3880 * value. So that gets subtracted from CPERLIN.
3883 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3884 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3885 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3889 len
++; /* Add in equal sign */
3893 * We're using maxfit as a marker for how many characters we can
3894 * fit into the line. Bump it by two because we're not using quotes
3901 * If we don't have a charset or language tag in this parameter,
3905 if (! pm
->pm_charset
) {
3906 pm
->pm_charset
= getcpy(write_charset_8bit());
3907 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3908 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3909 "local character set is US-ASCII", pm
->pm_name
);
3912 pm
->pm_lang
= getcpy(NULL
); /* Default to a blank lang tag */
3914 len
++; /* For the encoding marker */
3917 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3922 * We know we definitely need to include an index. maxfit already
3923 * includes the section marker.
3925 len
+= strlen(indexchar
);
3927 for (p
= start
; *p
!= '\0'; p
++) {
3928 if (isparamencode(*p
)) {
3936 * Just so there's no confusion: maxfit is counting OUTPUT
3937 * characters (post-encoding). fit is counting INPUT characters.
3939 if (! fitlimit
&& maxfit
>= 0)
3941 else if (! fitlimit
)
3946 * Calculate the string length, but add room for quoting \
3947 * and " if necessary. Also account for quotes at beginning
3950 for (p
= start
; *p
!= '\0'; p
++) {
3961 if (! fitlimit
&& maxfit
>= 0)
3963 else if (! fitlimit
)
3980 * Output an encoded parameter string.
3984 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3985 size_t valueoff
, int index
)
3987 size_t outlen
= 0, n
;
3988 char *endptr
= output
+ len
, *p
;
3991 * First, output the marker for an encoded string.
3999 * If the index is 0, output the character set and language tag.
4000 * If theses were NULL, they should have already been filled in
4005 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
4009 if (output
> endptr
) {
4010 advise(NULL
, "Internal error: parameter buffer overflow");
4016 * Copy over the value, encoding if necessary
4019 p
= pm
->pm_value
+ valueoff
;
4020 while (valuelen
-- > 0) {
4021 if (isparamencode(*p
)) {
4022 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4029 if (output
> endptr
) {
4030 advise(NULL
, "Internal error: parameter buffer overflow");
4041 * Output a "normal" parameter, without encoding. Be sure to escape
4042 * quotes and backslashes if necessary.
4046 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4050 char *endptr
= output
+ len
, *p
;
4056 p
= pm
->pm_value
+ valueoff
;
4058 while (valuelen
-- > 0) {
4068 if (output
> endptr
) {
4069 advise(NULL
, "Internal error: parameter buffer overflow");
4074 if (output
- 2 > endptr
) {
4075 advise(NULL
, "Internal error: parameter buffer overflow");
4086 * Add a parameter to the parameter linked list
4090 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4092 PM pm
= mh_xmalloc(sizeof(*pm
));
4094 memset(pm
, 0, sizeof(*pm
));
4096 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4097 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4100 (*last
)->pm_next
= pm
;
4111 * Either replace a current parameter with a new value, or add the parameter
4112 * to the parameter linked list.
4116 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4120 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4121 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4123 * If nocopy is set, it's assumed that we own both name
4124 * and value. We don't need name, so we discard it now.
4129 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4134 return add_param(first
, last
, name
, value
, nocopy
);
4138 * Retrieve a parameter value from a parameter linked list. If the parameter
4139 * value needs converted to the local character set, do that now.
4143 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4145 while (first
!= NULL
) {
4146 if (strcasecmp(name
, first
->pm_name
) == 0) {
4148 return first
->pm_value
;
4150 return getcpy(get_param_value(first
, replace
));
4152 first
= first
->pm_next
;
4159 * Return a parameter value, converting to the local character set if
4163 char *get_param_value(PM pm
, char replace
)
4165 static char buffer
[4096]; /* I hope no parameters are larger */
4166 size_t bufsize
= sizeof(buffer
);
4171 ICONV_CONST
char *p
;
4172 #else /* HAVE_ICONV */
4174 #endif /* HAVE_ICONV */
4179 * If we don't have a character set indicated, it's assumed to be
4180 * US-ASCII. If it matches our character set, we don't need to convert
4184 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4185 strlen(pm
->pm_charset
))) {
4186 return pm
->pm_value
;
4190 * In this case, we need to convert. If we have iconv support, use
4191 * that. Otherwise, go through and simply replace every non-ASCII
4192 * character with the substitution character.
4197 bufsize
= sizeof(buffer
);
4198 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4200 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4201 if (cd
== (iconv_t
) -1) {
4205 inbytes
= strlen(pm
->pm_value
);
4209 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4210 if (errno
!= EILSEQ
) {
4215 * Reset shift state, substitute our character,
4216 * try to restart conversion.
4219 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4232 for (++p
, --inbytes
;
4233 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4252 #endif /* HAVE_ICONV */
4255 * Take everything non-ASCII and substituite the replacement character
4259 bufsize
= sizeof(buffer
);
4260 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4261 if (isascii((unsigned char) *p
) && !iscntrl((unsigned char) *p
))