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.
12 #include <h/signals.h>
17 #include <h/mhparse.h>
21 #endif /* HAVE_ICONV */
27 extern int rcachesw
; /* mhcachesbr.c */
28 extern int wcachesw
; /* mhcachesbr.c */
30 int checksw
= 0; /* check Content-MD5 field */
33 * These are for mhfixmsg to:
34 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
36 * 2) Suppress the warning about bogus multipart content, and report it.
37 * 3) Suppress the warning about extraneous trailing ';' in header parameter
38 * lists, and report it.
40 int skip_mp_cte_check
;
41 int suppress_bogus_mp_content_warning
;
43 int suppress_extraneous_trailing_semicolon_warning
;
44 int extraneous_trailing_semicolon
;
47 * Structures for TEXT messages
49 struct k2v SubText
[] = {
50 { "plain", TEXT_PLAIN
},
51 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC-1341 */
52 { "enriched", TEXT_ENRICHED
}, /* defined in RFC-1896 */
53 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
56 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
59 * Structures for MULTIPART messages
61 struct k2v SubMultiPart
[] = {
62 { "mixed", MULTI_MIXED
},
63 { "alternative", MULTI_ALTERNATE
},
64 { "digest", MULTI_DIGEST
},
65 { "parallel", MULTI_PARALLEL
},
66 { "related", MULTI_RELATED
},
67 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
71 * Structures for MESSAGE messages
73 struct k2v SubMessage
[] = {
74 { "rfc822", MESSAGE_RFC822
},
75 { "partial", MESSAGE_PARTIAL
},
76 { "external-body", MESSAGE_EXTERNAL
},
77 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
81 * Structure for APPLICATION messages
83 struct k2v SubApplication
[] = {
84 { "octet-stream", APPLICATION_OCTETS
},
85 { "postscript", APPLICATION_POSTSCRIPT
},
86 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
90 * Mapping of names of CTE types in mhbuild directives
92 static struct k2v EncodingType
[] = {
96 { "quoted-printable", CE_QUOTED
},
98 { "base64", CE_BASE64
},
104 int find_cache (CT
, int, int *, char *, char *, int);
107 int part_ok (CT
, int);
108 int type_ok (CT
, int);
109 void content_error (char *, CT
, char *, ...);
112 void free_encoding (CT
, int);
117 static CT
get_content (FILE *, char *, int);
118 static int get_comment (const char *, const char *, char **, char **);
120 static int InitGeneric (CT
);
121 static int InitText (CT
);
122 static int InitMultiPart (CT
);
123 static void reverse_parts (CT
);
124 static int InitMessage (CT
);
125 static int InitApplication (CT
);
126 static int init_encoding (CT
, OpenCEFunc
);
127 static unsigned long size_encoding (CT
);
128 static int InitBase64 (CT
);
129 static int openBase64 (CT
, char **);
130 static int InitQuoted (CT
);
131 static int openQuoted (CT
, char **);
132 static int Init7Bit (CT
);
133 static int openExternal (CT
, CT
, CE
, char **, int *);
134 static int InitFile (CT
);
135 static int openFile (CT
, char **);
136 static int InitFTP (CT
);
137 static int openFTP (CT
, char **);
138 static int InitMail (CT
);
139 static int openMail (CT
, char **);
140 static int readDigest (CT
, char *);
141 static int get_leftover_mp_content (CT
, int);
142 static int InitURL (CT
);
143 static int openURL (CT
, char **);
144 static int parse_header_attrs (const char *, const char *, char **, PM
*,
146 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
147 static size_t encode_param(PM
, char *, size_t, size_t, size_t, int);
148 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
149 static int get_dispo (char *, CT
, int);
151 struct str2init str2cts
[] = {
152 { "application", CT_APPLICATION
, InitApplication
},
153 { "audio", CT_AUDIO
, InitGeneric
},
154 { "image", CT_IMAGE
, InitGeneric
},
155 { "message", CT_MESSAGE
, InitMessage
},
156 { "multipart", CT_MULTIPART
, InitMultiPart
},
157 { "text", CT_TEXT
, InitText
},
158 { "video", CT_VIDEO
, InitGeneric
},
159 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
160 { NULL
, CT_UNKNOWN
, NULL
},
163 struct str2init str2ces
[] = {
164 { "base64", CE_BASE64
, InitBase64
},
165 { "quoted-printable", CE_QUOTED
, InitQuoted
},
166 { "8bit", CE_8BIT
, Init7Bit
},
167 { "7bit", CE_7BIT
, Init7Bit
},
168 { "binary", CE_BINARY
, Init7Bit
},
169 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
170 { NULL
, CE_UNKNOWN
, NULL
},
174 * NOTE WELL: si_key MUST NOT have value of NOTOK
176 * si_key is 1 if access method is anonymous.
178 struct str2init str2methods
[] = {
179 { "afs", 1, InitFile
},
180 { "anon-ftp", 1, InitFTP
},
181 { "ftp", 0, InitFTP
},
182 { "local-file", 0, InitFile
},
183 { "mail-server", 0, InitMail
},
184 { "url", 0, InitURL
},
190 * Main entry point for parsing a MIME message or file.
191 * It returns the Content structure for the top level
192 * entity in the file.
196 parse_mime (char *file
)
205 * Check if file is actually standard input
207 if ((is_stdin
= !(strcmp (file
, "-")))) {
208 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
210 advise("mhparse", "unable to create temporary file in %s",
214 file
= add (tfile
, NULL
);
216 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
217 if (fwrite(buffer
, 1, n
, fp
) != n
) {
218 (void) m_unlink (file
);
219 advise (file
, "error copying to temporary file");
225 if (ferror (stdin
)) {
226 (void) m_unlink (file
);
227 advise ("stdin", "error reading");
231 (void) m_unlink (file
);
232 advise (file
, "error writing");
235 fseek (fp
, 0L, SEEK_SET
);
236 } else if ((fp
= fopen (file
, "r")) == NULL
) {
237 advise (file
, "unable to read");
241 if (!(ct
= get_content (fp
, file
, 1))) {
243 (void) m_unlink (file
);
244 advise (NULL
, "unable to decode %s", file
);
249 ct
->c_unlink
= 1; /* temp file to remove */
253 if (ct
->c_end
== 0L) {
254 fseek (fp
, 0L, SEEK_END
);
255 ct
->c_end
= ftell (fp
);
258 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
270 * Main routine for reading/parsing the headers
271 * of a message content.
273 * toplevel = 1 # we are at the top level of the message
274 * toplevel = 0 # we are inside message type or multipart type
275 * # other than multipart/digest
276 * toplevel = -1 # we are inside multipart/digest
277 * NB: on failure we will fclose(in)!
281 get_content (FILE *in
, char *file
, int toplevel
)
284 char buf
[BUFSIZ
], name
[NAMESZ
];
288 m_getfld_state_t gstate
= 0;
290 /* allocate the content structure */
291 if (!(ct
= (CT
) mh_xcalloc (1, sizeof(*ct
))))
292 adios (NULL
, "out of memory");
295 ct
->c_file
= add (file
, NULL
);
296 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
299 * Parse the header fields for this
300 * content into a linked list.
302 m_getfld_track_filepos (&gstate
, in
);
303 for (compnum
= 1;;) {
304 int bufsz
= sizeof buf
;
305 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
310 /* get copies of the buffers */
311 np
= add (name
, NULL
);
312 vp
= add (buf
, NULL
);
314 /* if necessary, get rest of field */
315 while (state
== FLDPLUS
) {
317 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
318 vp
= add (buf
, vp
); /* add to previous value */
321 /* Now add the header data to the list */
322 add_header (ct
, np
, vp
);
324 /* continue, to see if this isn't the last header field */
325 ct
->c_begin
= ftell (in
) + 1;
329 ct
->c_begin
= ftell (in
) - strlen (buf
);
333 ct
->c_begin
= ftell (in
);
338 adios (NULL
, "message format error in component #%d", compnum
);
341 adios (NULL
, "getfld() returned %d", state
);
344 /* break out of the loop */
347 m_getfld_state_destroy (&gstate
);
350 * Read the content headers. We will parse the
351 * MIME related header fields into their various
352 * structures and set internal flags related to
353 * content type/subtype, etc.
356 hp
= ct
->c_first_hf
; /* start at first header field */
358 /* Get MIME-Version field */
359 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
364 advise (NULL
, "message %s has multiple %s: fields",
365 ct
->c_file
, VRSN_FIELD
);
368 ct
->c_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
);
399 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
400 /* Get Content-Type field */
401 struct str2init
*s2i
;
402 CI ci
= &ct
->c_ctinfo
;
404 /* Check if we've already seen a Content-Type header */
406 advise (NULL
, "message %s has multiple %s: fields",
407 ct
->c_file
, TYPE_FIELD
);
411 /* Parse the Content-Type field */
412 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
416 * Set the Init function and the internal
417 * flag for this content type.
419 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
420 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
422 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
424 ct
->c_type
= s2i
->si_val
;
425 ct
->c_ctinitfnx
= s2i
->si_init
;
427 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
428 /* Get Content-Transfer-Encoding field */
430 struct str2init
*s2i
;
433 * Check if we've already seen the
434 * Content-Transfer-Encoding field
437 advise (NULL
, "message %s has multiple %s: fields",
438 ct
->c_file
, ENCODING_FIELD
);
442 /* get copy of this field */
443 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
445 while (isspace ((unsigned char) *cp
))
447 for (dp
= cp
; istoken (*dp
); dp
++)
453 * Find the internal flag and Init function
454 * for this transfer encoding.
456 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
457 if (!strcasecmp (cp
, s2i
->si_key
))
459 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
462 ct
->c_encoding
= s2i
->si_val
;
464 /* Call the Init function for this encoding */
465 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
468 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
469 /* Get Content-MD5 field */
475 if (ct
->c_digested
) {
476 advise (NULL
, "message %s has multiple %s: fields",
477 ct
->c_file
, MD5_FIELD
);
481 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
483 while (isspace ((unsigned char) *cp
))
485 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
487 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
488 if (!isspace ((unsigned char) *dp
))
492 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
495 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
500 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
508 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
509 /* Get Content-ID field */
510 ct
->c_id
= add (hp
->value
, ct
->c_id
);
512 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
513 /* Get Content-Description field */
514 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
516 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
517 /* Get Content-Disposition field */
518 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
523 hp
= hp
->next
; /* next header field */
527 * Check if we saw a Content-Type field.
528 * If not, then assign a default value for
529 * it, and the Init function.
533 * If we are inside a multipart/digest message,
534 * so default type is message/rfc822
537 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
539 ct
->c_type
= CT_MESSAGE
;
540 ct
->c_ctinitfnx
= InitMessage
;
543 * Else default type is text/plain
545 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
547 ct
->c_type
= CT_TEXT
;
548 ct
->c_ctinitfnx
= InitText
;
552 /* Use default Transfer-Encoding, if necessary */
554 ct
->c_encoding
= CE_7BIT
;
567 * small routine to add header field to list
571 add_header (CT ct
, char *name
, char *value
)
575 /* allocate header field structure */
576 hp
= mh_xmalloc (sizeof(*hp
));
578 /* link data into header structure */
583 /* link header structure into the list */
584 if (ct
->c_first_hf
== NULL
) {
585 ct
->c_first_hf
= hp
; /* this is the first */
588 ct
->c_last_hf
->next
= hp
; /* add it to the end */
597 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
598 * directives. Fills in the information of the CTinfo structure.
601 get_ctinfo (char *cp
, CT ct
, int magic
)
610 /* store copy of Content-Type line */
611 cp
= ct
->c_ctline
= add (cp
, NULL
);
613 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
616 /* change newlines to spaces */
617 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
620 /* trim trailing spaces */
621 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
622 if (!isspace ((unsigned char) *dp
))
627 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
629 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
630 &ci
->ci_comment
) == NOTOK
)
633 for (dp
= cp
; istoken (*dp
); dp
++)
636 ci
->ci_type
= add (cp
, NULL
); /* store content type */
640 advise (NULL
, "invalid %s: field in message %s (empty type)",
641 TYPE_FIELD
, ct
->c_file
);
645 /* down case the content type string */
646 for (dp
= ci
->ci_type
; *dp
; dp
++)
647 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
648 *dp
= tolower ((unsigned char) *dp
);
650 while (isspace ((unsigned char) *cp
))
653 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
654 &ci
->ci_comment
) == NOTOK
)
659 ci
->ci_subtype
= add ("", NULL
);
664 while (isspace ((unsigned char) *cp
))
667 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
668 &ci
->ci_comment
) == NOTOK
)
671 for (dp
= cp
; istoken (*dp
); dp
++)
674 ci
->ci_subtype
= add (cp
, NULL
); /* store the content subtype */
677 if (!*ci
->ci_subtype
) {
679 "invalid %s: field in message %s (empty subtype for \"%s\")",
680 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
684 /* down case the content subtype string */
685 for (dp
= ci
->ci_subtype
; *dp
; dp
++)
686 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
687 *dp
= tolower ((unsigned char) *dp
);
690 while (isspace ((unsigned char) *cp
))
693 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
694 &ci
->ci_comment
) == NOTOK
)
697 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
698 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
699 &ci
->ci_comment
)) != OK
) {
700 return status
== NOTOK
? NOTOK
: OK
;
704 * Get any <Content-Id> given in buffer
706 if (magic
&& *cp
== '<') {
711 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
712 advise (NULL
, "invalid ID in message %s", ct
->c_file
);
718 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
724 while (isspace ((unsigned char) *cp
))
729 * Get any [Content-Description] given in buffer.
731 if (magic
&& *cp
== '[') {
733 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
737 advise (NULL
, "invalid description in message %s", ct
->c_file
);
745 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
751 while (isspace ((unsigned char) *cp
))
756 * Get any {Content-Disposition} given in buffer.
758 if (magic
&& *cp
== '{') {
760 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
764 advise (NULL
, "invalid disposition in message %s", ct
->c_file
);
772 if (get_dispo(cp
, ct
, 1) != OK
)
778 while (isspace ((unsigned char) *cp
))
783 * Get any extension directives (right now just the content transfer
784 * encoding, but maybe others) that we care about.
787 if (magic
&& *cp
== '*') {
789 * See if it's a CTE we match on
794 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
798 advise (NULL
, "invalid null transfer encoding specification");
805 ct
->c_reqencoding
= CE_UNKNOWN
;
807 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
808 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
809 ct
->c_reqencoding
= kv
->kv_value
;
814 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
815 advise (NULL
, "invalid CTE specification: \"%s\"", dp
);
819 while (isspace ((unsigned char) *cp
))
824 * Check if anything is left over
828 ci
->ci_magic
= add (cp
, NULL
);
830 /* If there is a Content-Disposition header and it doesn't
831 have a *filename=, extract it from the magic contents.
832 The r1bindex call skips any leading directory
834 if (ct
->c_dispo_type
&&
835 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
836 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
837 r1bindex(ci
->ci_magic
, '/'), 0);
842 "extraneous information in message %s's %s: field\n%*s(%s)",
843 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
851 * Parse out a Content-Disposition header. A lot of this is cribbed from
855 get_dispo (char *cp
, CT ct
, int buildflag
)
857 char *dp
, *dispoheader
;
862 * Save the whole copy of the Content-Disposition header, unless we're
863 * processing a mhbuild directive. A NULL c_dispo will be a flag to
864 * mhbuild that the disposition header needs to be generated at that
868 dispoheader
= cp
= add(cp
, NULL
);
870 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
873 /* change newlines to spaces */
874 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
877 /* trim trailing spaces */
878 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
879 if (!isspace ((unsigned char) *dp
))
884 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
886 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
892 for (dp
= cp
; istoken (*dp
); dp
++)
895 ct
->c_dispo_type
= add (cp
, NULL
); /* store disposition type */
898 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
901 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
902 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
904 if (status
== NOTOK
) {
910 "extraneous information in message %s's %s: field\n%*s(%s)",
911 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
917 ct
->c_dispo
= dispoheader
;
924 get_comment (const char *filename
, const char *fieldname
, char **ap
,
929 char c
, buffer
[BUFSIZ
], *dp
;
939 advise (NULL
, "invalid comment in message %s's %s: field",
940 filename
, fieldname
);
945 if ((c
= *cp
++) == '\0')
968 if ((dp
= *commentp
)) {
969 *commentp
= concat (dp
, " ", buffer
, NULL
);
972 *commentp
= add (buffer
, NULL
);
976 while (isspace ((unsigned char) *cp
))
987 * Handles content types audio, image, and video.
988 * There's not much to do right here.
996 return OK
; /* not much to do here */
1007 char buffer
[BUFSIZ
];
1013 CI ci
= &ct
->c_ctinfo
;
1015 /* check for missing subtype */
1016 if (!*ci
->ci_subtype
)
1017 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1020 for (kv
= SubText
; kv
->kv_key
; kv
++)
1021 if (!strcasecmp (ci
->ci_subtype
, kv
->kv_key
))
1023 ct
->c_subtype
= kv
->kv_value
;
1025 /* allocate text character set structure */
1026 if ((t
= (struct text
*) mh_xcalloc (1, sizeof(*t
))) == NULL
)
1027 adios (NULL
, "out of memory");
1028 ct
->c_ctparams
= (void *) t
;
1030 /* scan for charset parameter */
1031 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1032 if (!strcasecmp (pm
->pm_name
, "charset"))
1035 /* check if content specified a character set */
1037 chset
= pm
->pm_value
;
1038 t
->tx_charset
= CHARSET_SPECIFIED
;
1040 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1044 * If we can not handle character set natively,
1045 * then check profile for string to modify the
1046 * terminal or display method.
1048 * termproc is for mhshow, though mhlist -debug prints it, too.
1050 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1051 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1052 if ((cp
= context_find (buffer
)))
1053 ct
->c_termproc
= getcpy (cp
);
1065 InitMultiPart (CT ct
)
1075 struct multipart
*m
;
1077 struct part
*part
, **next
;
1078 CI ci
= &ct
->c_ctinfo
;
1083 * The encoding for multipart messages must be either
1084 * 7bit, 8bit, or binary (per RFC2045).
1086 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1087 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1088 /* Copy the Content-Transfer-Encoding header field body so we can
1089 remove any trailing whitespace and leading blanks from it. */
1090 char *cte
= add (ct
->c_celine
? ct
->c_celine
: "(null)", NULL
);
1092 bp
= cte
+ strlen (cte
) - 1;
1093 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1094 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1097 "\"%s/%s\" type in message %s must be encoded in\n"
1098 "7bit, 8bit, or binary, per RFC 2045 (6.4). One workaround "
1099 "is to\nmanually edit the file and change the \"%s\"\n"
1100 "Content-Transfer-Encoding to one of those. For now",
1101 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1108 for (kv
= SubMultiPart
; kv
->kv_key
; kv
++)
1109 if (!strcasecmp (ci
->ci_subtype
, kv
->kv_key
))
1111 ct
->c_subtype
= kv
->kv_value
;
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 */
1134 if ((m
= (struct multipart
*) mh_xcalloc (1, sizeof(*m
))) == NULL
)
1135 adios (NULL
, "out of memory");
1136 ct
->c_ctparams
= (void *) m
;
1138 /* check if boundary parameter contains only whitespace characters */
1139 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1142 advise (NULL
, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1143 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1147 /* remove trailing whitespace from boundary parameter */
1148 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1149 if (!isspace ((unsigned char) *dp
))
1153 /* record boundary separators */
1154 m
->mp_start
= concat (bp
, "\n", NULL
);
1155 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1157 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1158 advise (ct
->c_file
, "unable to open for reading");
1162 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1164 next
= &m
->mp_parts
;
1168 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1173 if (bufp
[0] != '-' || bufp
[1] != '-')
1176 if (strcmp (bufp
+ 2, m
->mp_start
))
1179 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
1180 adios (NULL
, "out of memory");
1182 next
= &part
->mp_next
;
1184 if (!(p
= get_content (fp
, ct
->c_file
,
1185 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1193 fseek (fp
, pos
, SEEK_SET
);
1196 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1200 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1201 if (p
->c_end
< p
->c_begin
)
1202 p
->c_begin
= p
->c_end
;
1207 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1213 if (! suppress_bogus_mp_content_warning
) {
1214 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1216 bogus_mp_content
= 1;
1218 if (!inout
&& part
) {
1220 p
->c_end
= ct
->c_end
;
1222 if (p
->c_begin
>= p
->c_end
) {
1223 for (next
= &m
->mp_parts
; *next
!= part
;
1224 next
= &((*next
)->mp_next
))
1228 free ((char *) part
);
1233 /* reverse the order of the parts for multipart/alternative */
1234 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
1285 reverse_parts (CT ct
)
1287 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1291 /* Reverse the order of its parts by walking the mp_parts list
1292 and pushing each node to the front. */
1293 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1294 next
= part
->mp_next
;
1295 part
->mp_next
= m
->mp_parts
;
1303 /* parse_mime() arranges alternates in reverse (priority) order. This
1304 function can be used to reverse them back. This will put, for
1305 example, a text/plain part before a text/html part in a
1306 multipart/alternative part, for example, where it belongs. */
1308 reverse_alternative_parts (CT ct
) {
1309 if (ct
->c_type
== CT_MULTIPART
) {
1310 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1313 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1317 /* And call recursively on each part of a multipart. */
1318 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1319 reverse_alternative_parts (part
->mp_part
);
1333 CI ci
= &ct
->c_ctinfo
;
1335 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1337 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1338 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1342 /* check for missing subtype */
1343 if (!*ci
->ci_subtype
)
1344 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1347 for (kv
= SubMessage
; kv
->kv_key
; kv
++)
1348 if (!strcasecmp (ci
->ci_subtype
, kv
->kv_key
))
1350 ct
->c_subtype
= kv
->kv_value
;
1352 switch (ct
->c_subtype
) {
1353 case MESSAGE_RFC822
:
1356 case MESSAGE_PARTIAL
:
1361 if ((p
= (struct partial
*) mh_xcalloc (1, sizeof(*p
))) == NULL
)
1362 adios (NULL
, "out of memory");
1363 ct
->c_ctparams
= (void *) p
;
1365 /* scan for parameters "id", "number", and "total" */
1366 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1367 if (!strcasecmp (pm
->pm_name
, "id")) {
1368 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1371 if (!strcasecmp (pm
->pm_name
, "number")) {
1372 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1373 || p
->pm_partno
< 1) {
1376 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1377 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1378 ct
->c_file
, TYPE_FIELD
);
1383 if (!strcasecmp (pm
->pm_name
, "total")) {
1384 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1393 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1395 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1396 ci
->ci_type
, ci
->ci_subtype
,
1397 ct
->c_file
, TYPE_FIELD
);
1403 case MESSAGE_EXTERNAL
:
1410 if ((e
= (struct exbody
*) mh_xcalloc (1, sizeof(*e
))) == NULL
)
1411 adios (NULL
, "out of memory");
1412 ct
->c_ctparams
= (void *) e
;
1415 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1416 advise (ct
->c_file
, "unable to open for reading");
1420 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1422 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1430 p
->c_ceopenfnx
= NULL
;
1431 if ((exresult
= params_external (ct
, 0)) != NOTOK
1432 && p
->c_ceopenfnx
== openMail
) {
1436 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1438 content_error (NULL
, ct
,
1439 "empty body for access-type=mail-server");
1443 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1444 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1446 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1448 adios ("failed", "fread");
1451 adios (NULL
, "unexpected EOF from fread");
1454 bp
+= cc
, size
-= cc
;
1461 p
->c_end
= p
->c_begin
;
1466 if (exresult
== NOTOK
)
1468 if (e
->eb_flags
== NOTOK
)
1471 switch (p
->c_type
) {
1476 if (p
->c_subtype
!= MESSAGE_RFC822
)
1480 e
->eb_partno
= ct
->c_partno
;
1482 (*p
->c_ctinitfnx
) (p
);
1497 params_external (CT ct
, int composing
)
1500 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1501 CI ci
= &ct
->c_ctinfo
;
1503 ct
->c_ceopenfnx
= NULL
;
1504 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1505 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1506 struct str2init
*s2i
;
1507 CT p
= e
->eb_content
;
1509 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1510 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1513 e
->eb_access
= pm
->pm_value
;
1514 e
->eb_flags
= NOTOK
;
1515 p
->c_encoding
= CE_EXTERNAL
;
1518 e
->eb_access
= s2i
->si_key
;
1519 e
->eb_flags
= s2i
->si_val
;
1520 p
->c_encoding
= CE_EXTERNAL
;
1522 /* Call the Init function for this external type */
1523 if ((*s2i
->si_init
)(p
) == NOTOK
)
1527 if (!strcasecmp (pm
->pm_name
, "name")) {
1528 e
->eb_name
= pm
->pm_value
;
1531 if (!strcasecmp (pm
->pm_name
, "permission")) {
1532 e
->eb_permission
= pm
->pm_value
;
1535 if (!strcasecmp (pm
->pm_name
, "site")) {
1536 e
->eb_site
= pm
->pm_value
;
1539 if (!strcasecmp (pm
->pm_name
, "directory")) {
1540 e
->eb_dir
= pm
->pm_value
;
1543 if (!strcasecmp (pm
->pm_name
, "mode")) {
1544 e
->eb_mode
= pm
->pm_value
;
1547 if (!strcasecmp (pm
->pm_name
, "size")) {
1548 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1551 if (!strcasecmp (pm
->pm_name
, "server")) {
1552 e
->eb_server
= pm
->pm_value
;
1555 if (!strcasecmp (pm
->pm_name
, "subject")) {
1556 e
->eb_subject
= pm
->pm_value
;
1559 if (!strcasecmp (pm
->pm_name
, "url")) {
1561 * According to RFC 2017, we have to remove all whitespace from
1565 char *u
, *p
= pm
->pm_value
;
1566 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1568 for (; *p
!= '\0'; p
++) {
1569 if (! isspace((unsigned char) *p
))
1576 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1577 e
->eb_body
= getcpy (pm
->pm_value
);
1582 if (!e
->eb_access
) {
1584 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1585 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1598 InitApplication (CT ct
)
1601 CI ci
= &ct
->c_ctinfo
;
1604 for (kv
= SubApplication
; kv
->kv_key
; kv
++)
1605 if (!strcasecmp (ci
->ci_subtype
, kv
->kv_key
))
1607 ct
->c_subtype
= kv
->kv_value
;
1614 * TRANSFER ENCODINGS
1618 init_encoding (CT ct
, OpenCEFunc openfnx
)
1620 ct
->c_ceopenfnx
= openfnx
;
1621 ct
->c_ceclosefnx
= close_encoding
;
1622 ct
->c_cesizefnx
= size_encoding
;
1629 close_encoding (CT ct
)
1631 CE ce
= &ct
->c_cefile
;
1640 static unsigned long
1641 size_encoding (CT ct
)
1646 CE ce
= &ct
->c_cefile
;
1649 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1650 return (long) st
.st_size
;
1653 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1654 return (long) st
.st_size
;
1659 if (ct
->c_encoding
== CE_EXTERNAL
)
1660 return (ct
->c_end
- ct
->c_begin
);
1663 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1664 return (ct
->c_end
- ct
->c_begin
);
1666 if (fstat (fd
, &st
) != NOTOK
)
1667 size
= (long) st
.st_size
;
1671 (*ct
->c_ceclosefnx
) (ct
);
1680 static unsigned char b642nib
[0x80] = {
1681 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1682 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1683 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1684 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1685 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1686 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1687 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1688 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1689 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
1690 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1691 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1692 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1693 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1694 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1695 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1696 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1703 return init_encoding (ct
, openBase64
);
1708 openBase64 (CT ct
, char **file
)
1710 int bitno
, cc
, digested
;
1711 int fd
, len
, skip
, own_ct_fp
= 0, text
= ct
->c_type
== CT_TEXT
;
1713 unsigned char value
, b
;
1714 char *cp
, *ep
, buffer
[BUFSIZ
];
1715 /* sbeck -- handle suffixes */
1717 CE ce
= &ct
->c_cefile
;
1721 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1726 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1727 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1733 if (*file
== NULL
) {
1736 ce
->ce_file
= add (*file
, NULL
);
1740 /* sbeck@cise.ufl.edu -- handle suffixes */
1742 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1743 if (ce
->ce_unlink
) {
1744 /* Create temporary file with filename extension. */
1745 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1746 adios(NULL
, "unable to create temporary file in %s",
1750 ce
->ce_file
= add (cp
, ce
->ce_file
);
1752 } else if (*file
== NULL
) {
1754 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1755 adios(NULL
, "unable to create temporary file in %s",
1758 ce
->ce_file
= add (tempfile
, NULL
);
1761 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1762 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1766 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1767 adios (NULL
, "internal error(1)");
1770 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1771 content_error (ct
->c_file
, ct
, "unable to open for reading");
1777 if ((digested
= ct
->c_digested
))
1778 MD5Init (&mdContext
);
1784 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1786 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
1788 content_error (ct
->c_file
, ct
, "error reading from");
1792 content_error (NULL
, ct
, "premature eof");
1800 for (ep
= (cp
= buffer
) + cc
; cp
< ep
; cp
++) {
1803 if (isspace ((unsigned char) *cp
))
1805 if (skip
|| (((unsigned char) *cp
) & 0x80)
1806 || (value
= b642nib
[((unsigned char) *cp
) & 0x7f]) > 0x3f) {
1808 fprintf (stderr
, "*cp=0x%x pos=%ld skip=%d\n",
1809 (unsigned char) *cp
,
1810 (long) (lseek (fd
, (off_t
) 0, SEEK_CUR
) - (ep
- cp
)),
1813 content_error (NULL
, ct
,
1814 "invalid BASE64 encoding -- continuing");
1818 bits
|= value
<< bitno
;
1820 if ((bitno
-= 6) < 0) {
1821 b
= (bits
>> 16) & 0xff;
1822 if (!text
|| b
!= '\r')
1823 putc ((char) b
, ce
->ce_fp
);
1825 MD5Update (&mdContext
, &b
, 1);
1827 b
= (bits
>> 8) & 0xff;
1828 if (! text
|| b
!= '\r')
1829 putc ((char) b
, ce
->ce_fp
);
1831 MD5Update (&mdContext
, &b
, 1);
1834 if (! text
|| b
!= '\r')
1835 putc ((char) b
, ce
->ce_fp
);
1837 MD5Update (&mdContext
, &b
, 1);
1841 if (ferror (ce
->ce_fp
)) {
1842 content_error (ce
->ce_file
, ct
,
1843 "error writing to");
1846 bitno
= 18, bits
= 0L, skip
= 0;
1852 goto self_delimiting
;
1861 fprintf (stderr
, "premature ending (bitno %d)\n", bitno
);
1863 content_error (NULL
, ct
, "invalid BASE64 encoding");
1868 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1870 if (fflush (ce
->ce_fp
)) {
1871 content_error (ce
->ce_file
, ct
, "error writing to");
1876 unsigned char digest
[16];
1878 MD5Final (digest
, &mdContext
);
1879 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
1880 sizeof(digest
) / sizeof(digest
[0])))
1881 content_error (NULL
, ct
,
1882 "content integrity suspect (digest mismatch) -- continuing");
1885 fprintf (stderr
, "content integrity confirmed\n");
1888 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1891 *file
= ce
->ce_file
;
1896 return fileno (ce
->ce_fp
);
1903 free_encoding (ct
, 0);
1912 static char hex2nib
[0x80] = {
1913 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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,
1917 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1918 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1919 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1920 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1921 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1922 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1923 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1924 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1925 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1926 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1927 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1928 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1935 return init_encoding (ct
, openQuoted
);
1940 openQuoted (CT ct
, char **file
)
1942 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1948 CE ce
= &ct
->c_cefile
;
1949 /* sbeck -- handle suffixes */
1954 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1959 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1960 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1966 if (*file
== NULL
) {
1969 ce
->ce_file
= add (*file
, NULL
);
1973 /* sbeck@cise.ufl.edu -- handle suffixes */
1975 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1976 if (ce
->ce_unlink
) {
1977 /* Create temporary file with filename extension. */
1978 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1979 adios(NULL
, "unable to create temporary file in %s",
1983 ce
->ce_file
= add (cp
, ce
->ce_file
);
1985 } else if (*file
== NULL
) {
1987 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1988 adios(NULL
, "unable to create temporary file in %s",
1991 ce
->ce_file
= add (tempfile
, NULL
);
1994 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1995 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1999 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2000 adios (NULL
, "internal error(2)");
2003 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2004 content_error (ct
->c_file
, ct
, "unable to open for reading");
2010 if ((digested
= ct
->c_digested
))
2011 MD5Init (&mdContext
);
2018 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2020 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2021 content_error (NULL
, ct
, "premature eof");
2025 if ((cc
= gotlen
) > len
)
2029 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2030 if (!isspace ((unsigned char) *ep
))
2034 for (; cp
< ep
; cp
++) {
2036 /* in an escape sequence */
2038 /* at byte 1 of an escape sequence */
2039 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2040 /* next is byte 2 */
2043 /* at byte 2 of an escape sequence */
2045 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2046 putc (mask
, ce
->ce_fp
);
2048 MD5Update (&mdContext
, &mask
, 1);
2049 if (ferror (ce
->ce_fp
)) {
2050 content_error (ce
->ce_file
, ct
, "error writing to");
2053 /* finished escape sequence; next may be literal or a new
2054 * escape sequence */
2057 /* on to next byte */
2061 /* not in an escape sequence */
2063 /* starting an escape sequence, or invalid '='? */
2064 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2065 /* "=\n" soft line break, eat the \n */
2069 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2070 /* We don't have 2 bytes left, so this is an invalid
2071 * escape sequence; just show the raw bytes (below). */
2072 } else if (isxdigit ((unsigned char) cp
[1]) &&
2073 isxdigit ((unsigned char) cp
[2])) {
2074 /* Next 2 bytes are hex digits, making this a valid escape
2075 * sequence; let's decode it (above). */
2079 /* One or both of the next 2 is out of range, making this
2080 * an invalid escape sequence; just show the raw bytes
2085 /* Just show the raw byte. */
2086 putc (*cp
, ce
->ce_fp
);
2089 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2091 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2094 if (ferror (ce
->ce_fp
)) {
2095 content_error (ce
->ce_file
, ct
, "error writing to");
2101 content_error (NULL
, ct
,
2102 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2106 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2108 if (fflush (ce
->ce_fp
)) {
2109 content_error (ce
->ce_file
, ct
, "error writing to");
2114 unsigned char digest
[16];
2116 MD5Final (digest
, &mdContext
);
2117 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2118 sizeof(digest
) / sizeof(digest
[0])))
2119 content_error (NULL
, ct
,
2120 "content integrity suspect (digest mismatch) -- continuing");
2123 fprintf (stderr
, "content integrity confirmed\n");
2126 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2129 *file
= ce
->ce_file
;
2135 return fileno (ce
->ce_fp
);
2138 free_encoding (ct
, 0);
2155 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2158 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2164 open7Bit (CT ct
, char **file
)
2166 int cc
, fd
, len
, own_ct_fp
= 0;
2167 char buffer
[BUFSIZ
];
2168 /* sbeck -- handle suffixes */
2171 CE ce
= &ct
->c_cefile
;
2174 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2179 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2180 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2186 if (*file
== NULL
) {
2189 ce
->ce_file
= add (*file
, NULL
);
2193 /* sbeck@cise.ufl.edu -- handle suffixes */
2195 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2196 if (ce
->ce_unlink
) {
2197 /* Create temporary file with filename extension. */
2198 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2199 adios(NULL
, "unable to create temporary file in %s",
2203 ce
->ce_file
= add (cp
, ce
->ce_file
);
2205 } else if (*file
== NULL
) {
2207 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2208 adios(NULL
, "unable to create temporary file in %s",
2211 ce
->ce_file
= add (tempfile
, NULL
);
2214 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2215 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2219 if (ct
->c_type
== CT_MULTIPART
) {
2220 CI ci
= &ct
->c_ctinfo
;
2224 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2225 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2226 + 1 + strlen (ci
->ci_subtype
);
2227 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2230 fputs (buffer
, ce
->ce_fp
);
2234 if (ci
->ci_comment
) {
2235 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2236 fputs ("\n\t", ce
->ce_fp
);
2240 putc (' ', ce
->ce_fp
);
2243 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2246 fprintf (ce
->ce_fp
, "\n");
2248 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2250 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2252 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2253 fprintf (ce
->ce_fp
, "\n");
2256 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2257 adios (NULL
, "internal error(3)");
2260 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2261 content_error (ct
->c_file
, ct
, "unable to open for reading");
2267 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2269 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2271 content_error (ct
->c_file
, ct
, "error reading from");
2275 content_error (NULL
, ct
, "premature eof");
2283 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2284 advise ("open7Bit", "fwrite");
2286 if (ferror (ce
->ce_fp
)) {
2287 content_error (ce
->ce_file
, ct
, "error writing to");
2292 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2294 if (fflush (ce
->ce_fp
)) {
2295 content_error (ce
->ce_file
, ct
, "error writing to");
2299 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2302 *file
= ce
->ce_file
;
2307 return fileno (ce
->ce_fp
);
2310 free_encoding (ct
, 0);
2324 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2326 char cachefile
[BUFSIZ
];
2329 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2334 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2335 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2341 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2342 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2343 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2344 ce
->ce_file
= getcpy (cachefile
);
2348 admonish (cachefile
, "unable to fopen for reading");
2352 *fd
= fileno (ce
->ce_fp
);
2356 *file
= ce
->ce_file
;
2357 *fd
= fileno (ce
->ce_fp
);
2368 return init_encoding (ct
, openFile
);
2373 openFile (CT ct
, char **file
)
2376 char cachefile
[BUFSIZ
];
2377 struct exbody
*e
= ct
->c_ctexbody
;
2378 CE ce
= &ct
->c_cefile
;
2380 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2392 content_error (NULL
, ct
, "missing name parameter");
2396 ce
->ce_file
= getcpy (e
->eb_name
);
2399 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2400 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2404 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2405 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2406 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2410 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2411 if ((fp
= fopen (cachefile
, "w"))) {
2413 char buffer
[BUFSIZ
];
2414 FILE *gp
= ce
->ce_fp
;
2416 fseek (gp
, 0L, SEEK_SET
);
2418 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2420 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2421 advise ("openFile", "fwrite");
2426 admonish (ce
->ce_file
, "error reading");
2427 (void) m_unlink (cachefile
);
2431 admonish (cachefile
, "error writing");
2432 (void) m_unlink (cachefile
);
2439 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2440 *file
= ce
->ce_file
;
2441 return fileno (ce
->ce_fp
);
2451 return init_encoding (ct
, openFTP
);
2456 openFTP (CT ct
, char **file
)
2458 int cachetype
, caching
, fd
;
2460 char *bp
, *ftp
, *user
, *pass
;
2461 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2463 CE ce
= &ct
->c_cefile
;
2464 static char *username
= NULL
;
2465 static char *password
= NULL
;
2469 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2475 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2486 if (!e
->eb_name
|| !e
->eb_site
) {
2487 content_error (NULL
, ct
, "missing %s parameter",
2488 e
->eb_name
? "site": "name");
2492 /* Get the buffer ready to go */
2494 buflen
= sizeof(buffer
);
2497 * Construct the query message for user
2499 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2505 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2511 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2512 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2517 if (e
->eb_size
> 0) {
2518 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2523 snprintf (bp
, buflen
, "? ");
2526 * Now, check the answer
2528 if (!getanswer (buffer
))
2533 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2537 ruserpass (e
->eb_site
, &username
, &password
);
2542 ce
->ce_unlink
= (*file
== NULL
);
2544 cachefile
[0] = '\0';
2545 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2546 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2547 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2548 if (*file
== NULL
) {
2555 ce
->ce_file
= add (*file
, NULL
);
2557 ce
->ce_file
= add (cachefile
, NULL
);
2560 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2561 adios(NULL
, "unable to create temporary file in %s",
2564 ce
->ce_file
= add (tempfile
, NULL
);
2567 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2568 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2573 int child_id
, i
, vecp
;
2577 vec
[vecp
++] = r1bindex (ftp
, '/');
2578 vec
[vecp
++] = e
->eb_site
;
2581 vec
[vecp
++] = e
->eb_dir
;
2582 vec
[vecp
++] = e
->eb_name
;
2583 vec
[vecp
++] = ce
->ce_file
,
2584 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2585 ? "ascii" : "binary";
2590 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2594 adios ("fork", "unable to");
2598 close (fileno (ce
->ce_fp
));
2600 fprintf (stderr
, "unable to exec ");
2606 if (pidXwait (child_id
, NULL
)) {
2607 username
= password
= NULL
;
2617 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2622 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2623 if ((fp
= fopen (cachefile
, "w"))) {
2625 FILE *gp
= ce
->ce_fp
;
2627 fseek (gp
, 0L, SEEK_SET
);
2629 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2631 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2632 advise ("openFTP", "fwrite");
2637 admonish (ce
->ce_file
, "error reading");
2638 (void) m_unlink (cachefile
);
2642 admonish (cachefile
, "error writing");
2643 (void) m_unlink (cachefile
);
2651 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2652 *file
= ce
->ce_file
;
2653 return fileno (ce
->ce_fp
);
2664 return init_encoding (ct
, openMail
);
2669 openMail (CT ct
, char **file
)
2671 int child_id
, fd
, i
, vecp
;
2673 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2674 struct exbody
*e
= ct
->c_ctexbody
;
2675 CE ce
= &ct
->c_cefile
;
2677 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2688 if (!e
->eb_server
) {
2689 content_error (NULL
, ct
, "missing server parameter");
2693 /* Get buffer ready to go */
2695 buflen
= sizeof(buffer
);
2697 /* Now, construct query message */
2698 snprintf (bp
, buflen
, "Retrieve content");
2704 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2710 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2712 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2714 /* Now, check answer */
2715 if (!getanswer (buffer
))
2719 vec
[vecp
++] = r1bindex (mailproc
, '/');
2720 vec
[vecp
++] = e
->eb_server
;
2721 vec
[vecp
++] = "-subject";
2722 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2723 vec
[vecp
++] = "-body";
2724 vec
[vecp
++] = e
->eb_body
;
2727 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2731 advise ("fork", "unable to");
2735 execvp (mailproc
, vec
);
2736 fprintf (stderr
, "unable to exec ");
2742 if (pidXwait (child_id
, NULL
) == OK
)
2743 advise (NULL
, "request sent");
2747 if (*file
== NULL
) {
2749 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2750 adios(NULL
, "unable to create temporary file in %s",
2753 ce
->ce_file
= add (tempfile
, NULL
);
2756 ce
->ce_file
= add (*file
, NULL
);
2760 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2761 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2765 /* showproc is for mhshow and mhstore, though mhlist -debug
2766 * prints it, too. */
2768 free (ct
->c_showproc
);
2769 ct
->c_showproc
= add ("true", NULL
);
2771 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2772 *file
= ce
->ce_file
;
2773 return fileno (ce
->ce_fp
);
2784 return init_encoding (ct
, openURL
);
2789 openURL (CT ct
, char **file
)
2791 struct exbody
*e
= ct
->c_ctexbody
;
2792 CE ce
= &ct
->c_cefile
;
2793 char *urlprog
, *program
;
2794 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2795 int fd
, caching
, cachetype
;
2796 struct msgs_array args
= { 0, 0, NULL
};
2799 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2803 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2807 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2819 content_error(NULL
, ct
, "missing url parameter");
2823 ce
->ce_unlink
= (*file
== NULL
);
2825 cachefile
[0] = '\0';
2827 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2828 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2829 if (*file
== NULL
) {
2836 ce
->ce_file
= add(*file
, NULL
);
2838 ce
->ce_file
= add(cachefile
, NULL
);
2841 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2842 adios(NULL
, "unable to create temporary file in %s",
2845 ce
->ce_file
= add (tempfile
, NULL
);
2848 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2849 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2853 switch (child_id
= fork()) {
2855 adios ("fork", "unable to");
2859 argsplit_msgarg(&args
, urlprog
, &program
);
2860 app_msgarg(&args
, e
->eb_url
);
2861 app_msgarg(&args
, NULL
);
2862 dup2(fileno(ce
->ce_fp
), 1);
2863 close(fileno(ce
->ce_fp
));
2864 execvp(program
, args
.msgs
);
2865 fprintf(stderr
, "Unable to exec ");
2871 if (pidXwait(child_id
, NULL
)) {
2879 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2884 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2885 if ((fp
= fopen(cachefile
, "w"))) {
2887 FILE *gp
= ce
->ce_fp
;
2889 fseeko(gp
, 0, SEEK_SET
);
2891 while ((cc
= fread(buffer
, sizeof(*buffer
),
2892 sizeof(buffer
), gp
)) > 0)
2893 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2894 advise ("openURL", "fwrite");
2900 admonish(ce
->ce_file
, "error reading");
2901 (void) m_unlink (cachefile
);
2908 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2909 *file
= ce
->ce_file
;
2914 readDigest (CT ct
, char *cp
)
2919 unsigned char *dp
, value
, *ep
;
2925 for (ep
= (dp
= ct
->c_digest
)
2926 + sizeof(ct
->c_digest
) / sizeof(ct
->c_digest
[0]); *cp
; cp
++)
2931 || (value
= b642nib
[*cp
& 0x7f]) > 0x3f) {
2933 fprintf (stderr
, "invalid BASE64 encoding\n");
2937 bits
|= value
<< bitno
;
2939 if ((bitno
-= 6) < 0) {
2940 if (dp
+ (3 - skip
) > ep
)
2941 goto invalid_digest
;
2942 *dp
++ = (bits
>> 16) & 0xff;
2944 *dp
++ = (bits
>> 8) & 0xff;
2946 *dp
++ = bits
& 0xff;
2956 goto self_delimiting
;
2961 fprintf (stderr
, "premature ending (bitno %d)\n", bitno
);
2971 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2979 fprintf (stderr
, "MD5 digest=");
2980 for (dp
= ct
->c_digest
; dp
< ep
; dp
++)
2981 fprintf (stderr
, "%02x", *dp
& 0xff);
2982 fprintf (stderr
, "\n");
2989 /* Multipart parts might have content before the first subpart and/or
2990 after the last subpart that hasn't been stored anywhere else, so do
2993 get_leftover_mp_content (CT ct
, int before
/* or after */)
2995 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2997 int found_boundary
= 0;
3003 char *content
= NULL
;
3005 if (! m
) return NOTOK
;
3008 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
3010 /* Isolate the beginning of this part to the beginning of the
3011 first subpart and save any content between them. */
3012 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
3013 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
3014 boundary
= concat ("--", m
->mp_start
, NULL
);
3016 struct part
*last_subpart
= NULL
;
3017 struct part
*subpart
;
3019 /* Go to the last subpart to get its end position. */
3020 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
3021 last_subpart
= subpart
;
3024 if (last_subpart
== NULL
) return NOTOK
;
3026 /* Isolate the end of the last subpart to the end of this part
3027 and save any content between them. */
3028 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
3029 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
3030 boundary
= concat ("--", m
->mp_stop
, NULL
);
3033 /* Back up by 1 to pick up the newline. */
3034 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
3036 /* Don't look beyond beginning of first subpart (before) or
3037 next part (after). */
3038 if (read
> max
) bufp
[read
-max
] = '\0';
3041 if (! strcmp (bufp
, boundary
)) {
3045 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3051 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3053 char *old_content
= content
;
3054 content
= concat (content
, bufp
, NULL
);
3058 ? concat ("\n", bufp
, NULL
)
3059 : concat (bufp
, NULL
);
3064 if (found_boundary
|| read
> max
) break;
3066 if (read
> max
) break;
3070 /* Skip the newline if that's all there is. */
3074 /* Remove trailing newline, except at EOF. */
3075 if ((before
|| ! feof (ct
->c_fp
)) &&
3076 (cp
= content
+ strlen (content
)) > content
&&
3081 if (strlen (content
) > 1) {
3083 m
->mp_content_before
= content
;
3085 m
->mp_content_after
= content
;
3100 ct_type_str (int type
) {
3102 case CT_APPLICATION
:
3103 return "application";
3119 return "unknown_type";
3125 ct_subtype_str (int type
, int subtype
) {
3127 case CT_APPLICATION
:
3129 case APPLICATION_OCTETS
:
3131 case APPLICATION_POSTSCRIPT
:
3132 return "postscript";
3134 return "unknown_app_subtype";
3138 case MESSAGE_RFC822
:
3140 case MESSAGE_PARTIAL
:
3142 case MESSAGE_EXTERNAL
:
3145 return "unknown_msg_subtype";
3151 case MULTI_ALTERNATE
:
3152 return "alternative";
3155 case MULTI_PARALLEL
:
3160 return "unknown_multipart_subtype";
3171 return "unknown_text_subtype";
3174 return "unknown_type";
3179 /* Find the content type and InitFunc for the CT. */
3180 const struct str2init
*
3181 get_ct_init (int type
) {
3182 const struct str2init
*sp
;
3184 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3185 if (type
== sp
->si_val
) {
3194 ce_str (int encoding
) {
3199 return "quoted-printable";
3215 /* Find the content type and InitFunc for the content encoding method. */
3216 const struct str2init
*
3217 get_ce_method (const char *method
) {
3218 struct str2init
*sp
;
3220 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3221 if (! strcasecmp (method
, sp
->si_key
)) {
3230 * Parse a series of MIME attributes (or parameters) given a header as
3233 * Arguments include:
3235 * filename - Name of input file (for error messages)
3236 * fieldname - Name of field being processed
3237 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3238 * Updated to point to end of attributes when finished.
3239 * param_head - Pointer to head of parameter list
3240 * param_tail - Pointer to tail of parameter list
3241 * commentp - Pointer to header comment pointer (may be NULL)
3243 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3244 * DONE to indicate a benign error (minor parsing error, but the program
3249 parse_header_attrs (const char *filename
, const char *fieldname
,
3250 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3253 char *cp
= *header_attrp
;
3259 struct sectlist
*next
;
3265 struct sectlist
*sechead
;
3266 struct parmlist
*next
;
3267 } *pp
, *pp2
, *phead
= NULL
;
3269 while (*cp
== ';') {
3270 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3271 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3274 while (isspace ((unsigned char) *cp
))
3278 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3283 if (! suppress_extraneous_trailing_semicolon_warning
) {
3285 "extraneous trailing ';' in message %s's %s: "
3287 filename
, fieldname
);
3289 extraneous_trailing_semicolon
= 1;
3293 /* down case the attribute name */
3294 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3295 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
3296 *dp
= tolower ((unsigned char) *dp
);
3298 for (up
= dp
; isspace ((unsigned char) *dp
);)
3300 if (dp
== cp
|| *dp
!= '=') {
3302 "invalid parameter in message %s's %s: "
3303 "field\n%*sparameter %s (error detected at offset %d)",
3304 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3309 * To handle RFC 2231, we have to deal with the following extensions:
3311 * name*=encoded-value
3312 * name*<N>=part-N-of-a-parameter-value
3313 * name*<N>*=encoded-part-N-of-a-parameter-value
3316 * If there's a * right before the equal sign, it's encoded.
3317 * If there's a * and one or more digits, then it's section N.
3319 * Remember we can have one or the other, or both. cp points to
3320 * beginning of name, up points past the last character in the
3324 for (vp
= cp
; vp
< up
; vp
++) {
3325 if (*vp
== '*' && vp
< up
- 1) {
3328 } else if (*vp
== '*' && vp
== up
- 1) {
3330 } else if (partial
) {
3331 if (isdigit((unsigned char) *vp
))
3332 index
= *vp
- '0' + index
* 10;
3334 advise (NULL
, "invalid parameter index in message %s's "
3335 "%s: field\n%*s(parameter %s)", filename
,
3336 fieldname
, strlen(invo_name
) + 2, "", cp
);
3345 * Break out the parameter name and value sections and allocate
3349 nameptr
= mh_xmalloc(len
+ 1);
3350 strncpy(nameptr
, cp
, len
);
3351 nameptr
[len
] = '\0';
3353 for (dp
++; isspace ((unsigned char) *dp
);)
3358 * Single quotes delimit the character set and language tag.
3359 * They are required on the first section (or a complete
3364 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3370 charset
= mh_xmalloc(len
+ 1);
3371 strncpy(charset
, dp
, len
);
3372 charset
[len
] = '\0';
3378 advise(NULL
, "missing charset in message %s's %s: "
3379 "field\n%*s(parameter %s)", filename
, fieldname
,
3380 strlen(invo_name
) + 2, "", nameptr
);
3386 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3393 lang
= mh_xmalloc(len
+ 1);
3394 strncpy(lang
, dp
, len
);
3401 advise(NULL
, "missing language tag in message %s's %s: "
3402 "field\n%*s(parameter %s)", filename
, fieldname
,
3403 strlen(invo_name
) + 2, "", nameptr
);
3414 * At this point vp should be pointing at the beginning
3415 * of the encoded value/section. Continue until we reach
3416 * the end or get whitespace. But first, calculate the
3417 * length so we can allocate the correct buffer size.
3420 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3422 if (*(vp
+ 1) == '\0' ||
3423 !isxdigit((unsigned char) *(vp
+ 1)) ||
3424 *(vp
+ 2) == '\0' ||
3425 !isxdigit((unsigned char) *(vp
+ 2))) {
3426 advise(NULL
, "invalid encoded sequence in message "
3427 "%s's %s: field\n%*s(parameter %s)",
3428 filename
, fieldname
, strlen(invo_name
) + 2,
3442 up
= valptr
= mh_xmalloc(len
+ 1);
3444 for (vp
= dp
; istoken(*vp
); vp
++) {
3446 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3457 * A "normal" string. If it's got a leading quote, then we
3458 * strip the quotes out. Otherwise go until we reach the end
3459 * or get whitespace. Note we scan it twice; once to get the
3460 * length, then the second time copies it into the destination
3467 for (cp
= dp
+ 1;;) {
3472 "invalid quoted-string in message %s's %s: "
3473 "field\n%*s(parameter %s)",
3474 filename
, fieldname
, strlen(invo_name
) + 2, "",
3497 for (cp
= dp
; istoken (*cp
); cp
++) {
3502 valptr
= mh_xmalloc(len
+ 1);
3506 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3514 strncpy(valptr
, cp
= dp
, len
);
3522 * If 'partial' is set, we don't allocate a parameter now. We
3523 * put it on the parameter linked list to be reassembled later.
3525 * "phead" points to a list of all parameters we need to reassemble.
3526 * Each parameter has a list of sections. We insert the sections in
3531 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3532 if (strcasecmp(nameptr
, pp
->name
) == 0)
3537 pp
= mh_xmalloc(sizeof(*pp
));
3538 memset(pp
, 0, sizeof(*pp
));
3545 * Insert this into the section linked list
3548 sp
= mh_xmalloc(sizeof(*sp
));
3549 memset(sp
, 0, sizeof(*sp
));
3554 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3555 sp
->next
= pp
->sechead
;
3558 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3559 if (sp2
->index
== sp
->index
) {
3560 advise (NULL
, "duplicate index (%d) in message "
3561 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3562 filename
, fieldname
, strlen(invo_name
) + 2, "",
3567 if (sp2
->index
< sp
->index
&&
3568 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3569 sp
->next
= sp2
->next
;
3576 advise(NULL
, "Internal error: cannot insert partial "
3577 "param in message %s's %s: field\n%*s(parameter %s)",
3578 filename
, fieldname
, strlen(invo_name
) + 2, "",
3586 * Save our charset and lang tags.
3589 if (index
== 0 && encoded
) {
3592 pp
->charset
= charset
;
3598 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3599 pm
->pm_charset
= charset
;
3603 while (isspace ((unsigned char) *cp
))
3607 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3613 * Now that we're done, reassemble all of the partial parameters.
3616 for (pp
= phead
; pp
!= NULL
; ) {
3620 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3621 if (sp
->index
!= pindex
++) {
3622 advise(NULL
, "missing section %d for parameter in "
3623 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3624 filename
, fieldname
, strlen(invo_name
) + 2, "",
3631 p
= q
= mh_xmalloc(tlen
+ 1);
3632 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3633 memcpy(q
, sp
->value
, sp
->len
);
3643 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3644 pm
->pm_charset
= pp
->charset
;
3645 pm
->pm_lang
= pp
->lang
;
3656 * Return the charset for a particular content type.
3660 content_charset (CT ct
) {
3661 char *ret_charset
= NULL
;
3663 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3665 return ret_charset
? ret_charset
: getcpy ("US-ASCII");
3670 * Create a string based on a list of output parameters. Assume that this
3671 * parameter string will be appended to an existing header, so start out
3672 * with the separator (;). Perform RFC 2231 encoding when necessary.
3676 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3678 char *paramout
= NULL
;
3679 char line
[CPERLIN
* 2], *q
;
3680 int curlen
, index
, cont
, encode
, i
;
3681 size_t valoff
, numchars
;
3683 while (params
!= NULL
) {
3689 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3692 if (strlen(params
->pm_name
) > CPERLIN
) {
3693 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3699 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3702 * Loop until we get a parameter that fits within a line. We
3703 * assume new lines start with a tab, so check our overflow based
3713 * At this point we're definitely continuing the line, so
3714 * be sure to include the parameter name and section index.
3717 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3718 params
->pm_name
, index
);
3721 * Both of these functions do a NUL termination
3725 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3726 numchars
, valoff
, index
);
3728 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3739 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3744 * "line" starts with a ;\n\t, so that doesn't count against
3745 * the length. But add 8 since it starts with a tab; that's
3746 * how we end up with 5.
3749 initialwidth
= strlen(line
) + 5;
3752 * At this point the line should be built, so add it to our
3753 * current output buffer.
3756 paramout
= add(line
, paramout
);
3760 * If this won't fit on the line, start a new one. Save room in
3761 * case we need a semicolon on the end
3764 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3776 * At this point, we're either finishing a contined parameter, or
3777 * we're working on a new one.
3781 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3782 params
->pm_name
, index
);
3784 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3789 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3790 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3792 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3793 strlen(params
->pm_value
+ valoff
), valoff
);
3801 paramout
= add(line
, paramout
);
3802 initialwidth
+= strlen(line
);
3804 params
= params
->pm_next
;
3808 *offsetout
= initialwidth
;
3814 * Calculate the size of a parameter.
3818 * pm - The parameter being output
3819 * index - If continuing the parameter, the index of the section
3821 * valueoff - The current offset into the parameter value that we're
3822 * working on (previous sections have consumed valueoff bytes).
3823 * encode - Set if we should perform encoding on this parameter section
3824 * (given that we're consuming bytesfit bytes).
3825 * cont - Set if the remaining data in value will not fit on a single
3826 * line and will need to be continued.
3827 * bytesfit - The number of bytes that we can consume from the parameter
3828 * value and still fit on a completely new line. The
3829 * calculation assumes the new line starts with a tab,
3830 * includes the parameter name and any encoding, and fits
3831 * within CPERLIN bytes. Will always be at least 1.
3835 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3838 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3839 size_t len
= 0, fit
= 0;
3840 int fitlimit
= 0, eightbit
, maxfit
;
3845 * Add up the length. First, start with the parameter name.
3848 len
= strlen(pm
->pm_name
);
3851 * Scan the parameter value and see if we need to do encoding for this
3855 eightbit
= contains8bit(start
, NULL
);
3858 * Determine if we need to encode this section. Encoding is necessary if:
3860 * - There are any 8-bit characters at all and we're on the first
3862 * - There are 8-bit characters within N bytes of our section start.
3863 * N is calculated based on the number of bytes it would take to
3864 * reach CPERLIN. Specifically:
3865 * 8 (starting tab) +
3866 * strlen(param name) +
3867 * 4 ('* for section marker, '=', opening/closing '"')
3869 * is the number of bytes used by everything that isn't part of the
3870 * value. So that gets subtracted from CPERLIN.
3873 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3874 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3875 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3879 len
++; /* Add in equal sign */
3883 * We're using maxfit as a marker for how many characters we can
3884 * fit into the line. Bump it by two because we're not using quotes
3891 * If we don't have a charset or language tag in this parameter,
3895 if (! pm
->pm_charset
) {
3896 pm
->pm_charset
= getcpy(write_charset_8bit());
3897 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3898 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3899 "local character set is US-ASCII", pm
->pm_name
);
3902 pm
->pm_lang
= getcpy(NULL
); /* Default to a blank lang tag */
3904 len
++; /* For the encoding marker */
3907 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3912 * We know we definitely need to include an index. maxfit already
3913 * includes the section marker.
3915 len
+= strlen(indexchar
);
3917 for (p
= start
; *p
!= '\0'; p
++) {
3918 if (isparamencode(*p
)) {
3926 * Just so there's no confusion: maxfit is counting OUTPUT
3927 * characters (post-encoding). fit is counting INPUT characters.
3929 if (! fitlimit
&& maxfit
>= 0)
3931 else if (! fitlimit
)
3936 * Calculate the string length, but add room for quoting \
3937 * and " if necessary. Also account for quotes at beginning
3940 for (p
= start
; *p
!= '\0'; p
++) {
3951 if (! fitlimit
&& maxfit
>= 0)
3953 else if (! fitlimit
)
3970 * Output an encoded parameter string.
3974 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3975 size_t valueoff
, int index
)
3977 size_t outlen
= 0, n
;
3978 char *endptr
= output
+ len
, *p
;
3981 * First, output the marker for an encoded string.
3989 * If the index is 0, output the character set and language tag.
3990 * If theses were NULL, they should have already been filled in
3995 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3999 if (output
> endptr
) {
4000 advise(NULL
, "Internal error: parameter buffer overflow");
4006 * Copy over the value, encoding if necessary
4009 p
= pm
->pm_value
+ valueoff
;
4010 while (valuelen
-- > 0) {
4011 if (isparamencode(*p
)) {
4012 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4019 if (output
> endptr
) {
4020 advise(NULL
, "Internal error: parameter buffer overflow");
4031 * Output a "normal" parameter, without encoding. Be sure to escape
4032 * quotes and backslashes if necessary.
4036 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4040 char *endptr
= output
+ len
, *p
;
4046 p
= pm
->pm_value
+ valueoff
;
4048 while (valuelen
-- > 0) {
4058 if (output
> endptr
) {
4059 advise(NULL
, "Internal error: parameter buffer overflow");
4064 if (output
- 2 > endptr
) {
4065 advise(NULL
, "Internal error: parameter buffer overflow");
4076 * Add a parameter to the parameter linked list
4080 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4082 PM pm
= mh_xmalloc(sizeof(*pm
));
4084 memset(pm
, 0, sizeof(*pm
));
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) *q
) & 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
))