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.
38 int skip_mp_cte_check
;
39 int suppress_bogus_mp_content_warning
;
43 * Structures for TEXT messages
45 struct k2v SubText
[] = {
46 { "plain", TEXT_PLAIN
},
47 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC-1341 */
48 { "enriched", TEXT_ENRICHED
}, /* defined in RFC-1896 */
49 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
52 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
55 * Structures for MULTIPART messages
57 struct k2v SubMultiPart
[] = {
58 { "mixed", MULTI_MIXED
},
59 { "alternative", MULTI_ALTERNATE
},
60 { "digest", MULTI_DIGEST
},
61 { "parallel", MULTI_PARALLEL
},
62 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
66 * Structures for MESSAGE messages
68 struct k2v SubMessage
[] = {
69 { "rfc822", MESSAGE_RFC822
},
70 { "partial", MESSAGE_PARTIAL
},
71 { "external-body", MESSAGE_EXTERNAL
},
72 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
76 * Structure for APPLICATION messages
78 struct k2v SubApplication
[] = {
79 { "octet-stream", APPLICATION_OCTETS
},
80 { "postscript", APPLICATION_POSTSCRIPT
},
81 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
85 * Mapping of names of CTE types in mhbuild directives
87 static struct k2v EncodingType
[] = {
91 { "quoted-printable", CE_QUOTED
},
93 { "base64", CE_BASE64
},
99 int find_cache (CT
, int, int *, char *, char *, int);
102 int part_ok (CT
, int);
103 int type_ok (CT
, int);
104 void content_error (char *, CT
, char *, ...);
107 void free_encoding (CT
, int);
112 static CT
get_content (FILE *, char *, int);
113 static int get_comment (const char *, const char *, char **, char **);
115 static int InitGeneric (CT
);
116 static int InitText (CT
);
117 static int InitMultiPart (CT
);
118 static void reverse_parts (CT
);
119 static int InitMessage (CT
);
120 static int InitApplication (CT
);
121 static int init_encoding (CT
, OpenCEFunc
);
122 static unsigned long size_encoding (CT
);
123 static int InitBase64 (CT
);
124 static int openBase64 (CT
, char **);
125 static int InitQuoted (CT
);
126 static int openQuoted (CT
, char **);
127 static int Init7Bit (CT
);
128 static int openExternal (CT
, CT
, CE
, char **, int *);
129 static int InitFile (CT
);
130 static int openFile (CT
, char **);
131 static int InitFTP (CT
);
132 static int openFTP (CT
, char **);
133 static int InitMail (CT
);
134 static int openMail (CT
, char **);
135 static int readDigest (CT
, char *);
136 static int get_leftover_mp_content (CT
, int);
137 static int InitURL (CT
);
138 static int openURL (CT
, char **);
139 static int parse_header_attrs (const char *, const char *, char **, PM
*,
141 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
142 static size_t encode_param(PM
, char *, size_t, size_t, size_t, int);
143 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
144 static int get_dispo (char *, CT
, int);
146 struct str2init str2cts
[] = {
147 { "application", CT_APPLICATION
, InitApplication
},
148 { "audio", CT_AUDIO
, InitGeneric
},
149 { "image", CT_IMAGE
, InitGeneric
},
150 { "message", CT_MESSAGE
, InitMessage
},
151 { "multipart", CT_MULTIPART
, InitMultiPart
},
152 { "text", CT_TEXT
, InitText
},
153 { "video", CT_VIDEO
, InitGeneric
},
154 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
155 { NULL
, CT_UNKNOWN
, NULL
},
158 struct str2init str2ces
[] = {
159 { "base64", CE_BASE64
, InitBase64
},
160 { "quoted-printable", CE_QUOTED
, InitQuoted
},
161 { "8bit", CE_8BIT
, Init7Bit
},
162 { "7bit", CE_7BIT
, Init7Bit
},
163 { "binary", CE_BINARY
, Init7Bit
},
164 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
165 { NULL
, CE_UNKNOWN
, NULL
},
169 * NOTE WELL: si_key MUST NOT have value of NOTOK
171 * si_key is 1 if access method is anonymous.
173 struct str2init str2methods
[] = {
174 { "afs", 1, InitFile
},
175 { "anon-ftp", 1, InitFTP
},
176 { "ftp", 0, InitFTP
},
177 { "local-file", 0, InitFile
},
178 { "mail-server", 0, InitMail
},
179 { "url", 0, InitURL
},
185 * Main entry point for parsing a MIME message or file.
186 * It returns the Content structure for the top level
187 * entity in the file.
191 parse_mime (char *file
)
200 * Check if file is actually standard input
202 if ((is_stdin
= !(strcmp (file
, "-")))) {
203 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
205 advise("mhparse", "unable to create temporary file in %s",
209 file
= add (tfile
, NULL
);
211 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
212 if (fwrite(buffer
, 1, n
, fp
) != n
) {
213 (void) m_unlink (file
);
214 advise (file
, "error copying to temporary file");
220 if (ferror (stdin
)) {
221 (void) m_unlink (file
);
222 advise ("stdin", "error reading");
226 (void) m_unlink (file
);
227 advise (file
, "error writing");
230 fseek (fp
, 0L, SEEK_SET
);
231 } else if ((fp
= fopen (file
, "r")) == NULL
) {
232 advise (file
, "unable to read");
236 if (!(ct
= get_content (fp
, file
, 1))) {
238 (void) m_unlink (file
);
239 advise (NULL
, "unable to decode %s", file
);
244 ct
->c_unlink
= 1; /* temp file to remove */
248 if (ct
->c_end
== 0L) {
249 fseek (fp
, 0L, SEEK_END
);
250 ct
->c_end
= ftell (fp
);
253 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
265 * Main routine for reading/parsing the headers
266 * of a message content.
268 * toplevel = 1 # we are at the top level of the message
269 * toplevel = 0 # we are inside message type or multipart type
270 * # other than multipart/digest
271 * toplevel = -1 # we are inside multipart/digest
272 * NB: on failure we will fclose(in)!
276 get_content (FILE *in
, char *file
, int toplevel
)
279 char buf
[BUFSIZ
], name
[NAMESZ
];
283 m_getfld_state_t gstate
= 0;
285 /* allocate the content structure */
286 if (!(ct
= (CT
) calloc (1, sizeof(*ct
))))
287 adios (NULL
, "out of memory");
290 ct
->c_file
= add (file
, NULL
);
291 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
294 * Parse the header fields for this
295 * content into a linked list.
297 m_getfld_track_filepos (&gstate
, in
);
298 for (compnum
= 1;;) {
299 int bufsz
= sizeof buf
;
300 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
305 /* get copies of the buffers */
306 np
= add (name
, NULL
);
307 vp
= add (buf
, NULL
);
309 /* if necessary, get rest of field */
310 while (state
== FLDPLUS
) {
312 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
313 vp
= add (buf
, vp
); /* add to previous value */
316 /* Now add the header data to the list */
317 add_header (ct
, np
, vp
);
319 /* continue, to see if this isn't the last header field */
320 ct
->c_begin
= ftell (in
) + 1;
324 ct
->c_begin
= ftell (in
) - strlen (buf
);
328 ct
->c_begin
= ftell (in
);
333 adios (NULL
, "message format error in component #%d", compnum
);
336 adios (NULL
, "getfld() returned %d", state
);
339 /* break out of the loop */
342 m_getfld_state_destroy (&gstate
);
345 * Read the content headers. We will parse the
346 * MIME related header fields into their various
347 * structures and set internal flags related to
348 * content type/subtype, etc.
351 hp
= ct
->c_first_hf
; /* start at first header field */
353 /* Get MIME-Version field */
354 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
359 advise (NULL
, "message %s has multiple %s: fields",
360 ct
->c_file
, VRSN_FIELD
);
363 ct
->c_vrsn
= add (hp
->value
, NULL
);
365 /* Now, cleanup this field */
368 while (isspace ((unsigned char) *cp
))
370 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
372 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
373 if (!isspace ((unsigned char) *dp
))
377 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
380 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
383 for (dp
= cp
; istoken (*dp
); dp
++)
387 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
390 admonish (NULL
, "message %s has unknown value for %s: field (%s)",
391 ct
->c_file
, VRSN_FIELD
, cp
);
394 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
395 /* Get Content-Type field */
396 struct str2init
*s2i
;
397 CI ci
= &ct
->c_ctinfo
;
399 /* Check if we've already seen a Content-Type header */
401 advise (NULL
, "message %s has multiple %s: fields",
402 ct
->c_file
, TYPE_FIELD
);
406 /* Parse the Content-Type field */
407 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
411 * Set the Init function and the internal
412 * flag for this content type.
414 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
415 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
417 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
419 ct
->c_type
= s2i
->si_val
;
420 ct
->c_ctinitfnx
= s2i
->si_init
;
422 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
423 /* Get Content-Transfer-Encoding field */
425 struct str2init
*s2i
;
428 * Check if we've already seen the
429 * Content-Transfer-Encoding field
432 advise (NULL
, "message %s has multiple %s: fields",
433 ct
->c_file
, ENCODING_FIELD
);
437 /* get copy of this field */
438 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
440 while (isspace ((unsigned char) *cp
))
442 for (dp
= cp
; istoken (*dp
); dp
++)
448 * Find the internal flag and Init function
449 * for this transfer encoding.
451 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
452 if (!strcasecmp (cp
, s2i
->si_key
))
454 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
457 ct
->c_encoding
= s2i
->si_val
;
459 /* Call the Init function for this encoding */
460 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
463 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
464 /* Get Content-MD5 field */
470 if (ct
->c_digested
) {
471 advise (NULL
, "message %s has multiple %s: fields",
472 ct
->c_file
, MD5_FIELD
);
476 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
478 while (isspace ((unsigned char) *cp
))
480 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
482 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
483 if (!isspace ((unsigned char) *dp
))
487 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
490 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
495 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
503 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
504 /* Get Content-ID field */
505 ct
->c_id
= add (hp
->value
, ct
->c_id
);
507 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
508 /* Get Content-Description field */
509 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
511 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
512 /* Get Content-Disposition field */
513 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
518 hp
= hp
->next
; /* next header field */
522 * Check if we saw a Content-Type field.
523 * If not, then assign a default value for
524 * it, and the Init function.
528 * If we are inside a multipart/digest message,
529 * so default type is message/rfc822
532 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
534 ct
->c_type
= CT_MESSAGE
;
535 ct
->c_ctinitfnx
= InitMessage
;
538 * Else default type is text/plain
540 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
542 ct
->c_type
= CT_TEXT
;
543 ct
->c_ctinitfnx
= InitText
;
547 /* Use default Transfer-Encoding, if necessary */
549 ct
->c_encoding
= CE_7BIT
;
562 * small routine to add header field to list
566 add_header (CT ct
, char *name
, char *value
)
570 /* allocate header field structure */
571 hp
= mh_xmalloc (sizeof(*hp
));
573 /* link data into header structure */
578 /* link header structure into the list */
579 if (ct
->c_first_hf
== NULL
) {
580 ct
->c_first_hf
= hp
; /* this is the first */
583 ct
->c_last_hf
->next
= hp
; /* add it to the end */
592 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
593 * directives. Fills in the information of the CTinfo structure.
596 get_ctinfo (char *cp
, CT ct
, int magic
)
605 /* store copy of Content-Type line */
606 cp
= ct
->c_ctline
= add (cp
, NULL
);
608 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
611 /* change newlines to spaces */
612 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
615 /* trim trailing spaces */
616 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
617 if (!isspace ((unsigned char) *dp
))
622 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
624 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
625 &ci
->ci_comment
) == NOTOK
)
628 for (dp
= cp
; istoken (*dp
); dp
++)
631 ci
->ci_type
= add (cp
, NULL
); /* store content type */
635 advise (NULL
, "invalid %s: field in message %s (empty type)",
636 TYPE_FIELD
, ct
->c_file
);
640 /* down case the content type string */
641 for (dp
= ci
->ci_type
; *dp
; dp
++)
642 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
643 *dp
= tolower ((unsigned char) *dp
);
645 while (isspace ((unsigned char) *cp
))
648 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
649 &ci
->ci_comment
) == NOTOK
)
654 ci
->ci_subtype
= add ("", NULL
);
659 while (isspace ((unsigned char) *cp
))
662 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
663 &ci
->ci_comment
) == NOTOK
)
666 for (dp
= cp
; istoken (*dp
); dp
++)
669 ci
->ci_subtype
= add (cp
, NULL
); /* store the content subtype */
672 if (!*ci
->ci_subtype
) {
674 "invalid %s: field in message %s (empty subtype for \"%s\")",
675 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
679 /* down case the content subtype string */
680 for (dp
= ci
->ci_subtype
; *dp
; dp
++)
681 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
682 *dp
= tolower ((unsigned char) *dp
);
685 while (isspace ((unsigned char) *cp
))
688 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
689 &ci
->ci_comment
) == NOTOK
)
692 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
693 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
694 &ci
->ci_comment
)) != OK
) {
695 return status
== NOTOK
? NOTOK
: OK
;
699 * Get any <Content-Id> given in buffer
701 if (magic
&& *cp
== '<') {
706 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
707 advise (NULL
, "invalid ID in message %s", ct
->c_file
);
713 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
719 while (isspace ((unsigned char) *cp
))
724 * Get any [Content-Description] given in buffer.
726 if (magic
&& *cp
== '[') {
728 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
732 advise (NULL
, "invalid description in message %s", ct
->c_file
);
740 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
746 while (isspace ((unsigned char) *cp
))
751 * Get any {Content-Disposition} given in buffer.
753 if (magic
&& *cp
== '{') {
755 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
759 advise (NULL
, "invalid disposition in message %s", ct
->c_file
);
767 if (get_dispo(cp
, ct
, 1) != OK
)
773 while (isspace ((unsigned char) *cp
))
778 * Get any extension directives (right now just the content transfer
779 * encoding, but maybe others) that we care about.
782 if (magic
&& *cp
== '*') {
784 * See if it's a CTE we match on
789 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
793 advise (NULL
, "invalid null transfer encoding specification");
800 ct
->c_reqencoding
= CE_UNKNOWN
;
802 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
803 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
804 ct
->c_reqencoding
= kv
->kv_value
;
809 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
810 advise (NULL
, "invalid CTE specification: \"%s\"", dp
);
814 while (isspace ((unsigned char) *cp
))
819 * Check if anything is left over
823 ci
->ci_magic
= add (cp
, NULL
);
825 /* If there is a Content-Disposition header and it doesn't
826 have a *filename=, extract it from the magic contents.
827 The r1bindex call skips any leading directory
829 if (ct
->c_dispo_type
&&
830 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
831 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
832 r1bindex(ci
->ci_magic
, '/'), 0);
837 "extraneous information in message %s's %s: field\n%*s(%s)",
838 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
846 * Parse out a Content-Disposition header. A lot of this is cribbed from
850 get_dispo (char *cp
, CT ct
, int buildflag
)
852 char *dp
, *dispoheader
;
857 * Save the whole copy of the Content-Disposition header, unless we're
858 * processing a mhbuild directive. A NULL c_dispo will be a flag to
859 * mhbuild that the disposition header needs to be generated at that
863 dispoheader
= cp
= add(cp
, NULL
);
865 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
868 /* change newlines to spaces */
869 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
872 /* trim trailing spaces */
873 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
874 if (!isspace ((unsigned char) *dp
))
879 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
881 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
887 for (dp
= cp
; istoken (*dp
); dp
++)
890 ct
->c_dispo_type
= add (cp
, NULL
); /* store disposition type */
893 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
896 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
897 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
899 if (status
== NOTOK
) {
905 "extraneous information in message %s's %s: field\n%*s(%s)",
906 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
912 ct
->c_dispo
= dispoheader
;
919 get_comment (const char *filename
, const char *fieldname
, char **ap
,
924 char c
, buffer
[BUFSIZ
], *dp
;
934 advise (NULL
, "invalid comment in message %s's %s: field",
935 filename
, fieldname
);
940 if ((c
= *cp
++) == '\0')
963 if ((dp
= *commentp
)) {
964 *commentp
= concat (dp
, " ", buffer
, NULL
);
967 *commentp
= add (buffer
, NULL
);
971 while (isspace ((unsigned char) *cp
))
982 * Handles content types audio, image, and video.
983 * There's not much to do right here.
991 return OK
; /* not much to do here */
1002 char buffer
[BUFSIZ
];
1008 CI ci
= &ct
->c_ctinfo
;
1010 /* check for missing subtype */
1011 if (!*ci
->ci_subtype
)
1012 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1015 for (kv
= SubText
; kv
->kv_key
; kv
++)
1016 if (!strcasecmp (ci
->ci_subtype
, kv
->kv_key
))
1018 ct
->c_subtype
= kv
->kv_value
;
1020 /* allocate text character set structure */
1021 if ((t
= (struct text
*) calloc (1, sizeof(*t
))) == NULL
)
1022 adios (NULL
, "out of memory");
1023 ct
->c_ctparams
= (void *) t
;
1025 /* scan for charset parameter */
1026 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1027 if (!strcasecmp (pm
->pm_name
, "charset"))
1030 /* check if content specified a character set */
1032 chset
= pm
->pm_value
;
1033 t
->tx_charset
= CHARSET_SPECIFIED
;
1035 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1039 * If we can not handle character set natively,
1040 * then check profile for string to modify the
1041 * terminal or display method.
1043 * termproc is for mhshow, though mhlist -debug prints it, too.
1045 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1046 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1047 if ((cp
= context_find (buffer
)))
1048 ct
->c_termproc
= getcpy (cp
);
1060 InitMultiPart (CT ct
)
1070 struct multipart
*m
;
1072 struct part
*part
, **next
;
1073 CI ci
= &ct
->c_ctinfo
;
1078 * The encoding for multipart messages must be either
1079 * 7bit, 8bit, or binary (per RFC2045).
1081 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1082 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1083 /* Copy the Content-Transfer-Encoding header field body so we can
1084 remove any trailing whitespace and leading blanks from it. */
1085 char *cte
= add (ct
->c_celine
? ct
->c_celine
: "(null)", NULL
);
1087 bp
= cte
+ strlen (cte
) - 1;
1088 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1089 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1092 "\"%s/%s\" type in message %s must be encoded in\n"
1093 "7bit, 8bit, or binary, per RFC 2045 (6.4). One workaround "
1094 "is to\nmanually edit the file and change the \"%s\"\n"
1095 "Content-Transfer-Encoding to one of those. For now",
1096 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1103 for (kv
= SubMultiPart
; kv
->kv_key
; kv
++)
1104 if (!strcasecmp (ci
->ci_subtype
, kv
->kv_key
))
1106 ct
->c_subtype
= kv
->kv_value
;
1109 * Check for "boundary" parameter, which is
1110 * required for multipart messages.
1113 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1114 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1120 /* complain if boundary parameter is missing */
1123 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1124 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1128 /* allocate primary structure for multipart info */
1129 if ((m
= (struct multipart
*) calloc (1, sizeof(*m
))) == NULL
)
1130 adios (NULL
, "out of memory");
1131 ct
->c_ctparams
= (void *) m
;
1133 /* check if boundary parameter contains only whitespace characters */
1134 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1137 advise (NULL
, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1138 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1142 /* remove trailing whitespace from boundary parameter */
1143 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1144 if (!isspace ((unsigned char) *dp
))
1148 /* record boundary separators */
1149 m
->mp_start
= concat (bp
, "\n", NULL
);
1150 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1152 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1153 advise (ct
->c_file
, "unable to open for reading");
1157 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1159 next
= &m
->mp_parts
;
1163 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1168 if (bufp
[0] != '-' || bufp
[1] != '-')
1171 if (strcmp (bufp
+ 2, m
->mp_start
))
1174 if ((part
= (struct part
*) calloc (1, sizeof(*part
))) == NULL
)
1175 adios (NULL
, "out of memory");
1177 next
= &part
->mp_next
;
1179 if (!(p
= get_content (fp
, ct
->c_file
,
1180 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1189 fseek (fp
, pos
, SEEK_SET
);
1192 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1196 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1197 if (p
->c_end
< p
->c_begin
)
1198 p
->c_begin
= p
->c_end
;
1203 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1209 if (! suppress_bogus_mp_content_warning
) {
1210 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1212 bogus_mp_content
= 1;
1214 if (!inout
&& part
) {
1216 p
->c_end
= ct
->c_end
;
1218 if (p
->c_begin
>= p
->c_end
) {
1219 for (next
= &m
->mp_parts
; *next
!= part
;
1220 next
= &((*next
)->mp_next
))
1224 free ((char *) part
);
1229 /* reverse the order of the parts for multipart/alternative */
1230 if (ct
->c_subtype
== MULTI_ALTERNATE
)
1234 * label all subparts with part number, and
1235 * then initialize the content of the subpart.
1240 char partnam
[BUFSIZ
];
1243 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1244 pp
= partnam
+ strlen (partnam
);
1249 for (part
= m
->mp_parts
, partnum
= 1; part
;
1250 part
= part
->mp_next
, partnum
++) {
1253 sprintf (pp
, "%d", partnum
);
1254 p
->c_partno
= add (partnam
, NULL
);
1256 /* initialize the content of the subparts */
1257 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1266 get_leftover_mp_content (ct
, 1);
1267 get_leftover_mp_content (ct
, 0);
1277 * reverse the order of the parts of a multipart/alternative
1281 reverse_parts (CT ct
)
1283 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1287 /* Reverse the order of its parts by walking the mp_parts list
1288 and pushing each node to the front. */
1289 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1290 next
= part
->mp_next
;
1291 part
->mp_next
= m
->mp_parts
;
1299 /* parse_mime() arranges alternates in reverse (priority) order. This
1300 function can be used to reverse them back. This will put, for
1301 example, a text/plain part before a text/html part in a
1302 multipart/alternative part, for example, where it belongs. */
1304 reverse_alternative_parts (CT ct
) {
1305 if (ct
->c_type
== CT_MULTIPART
) {
1306 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1309 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1313 /* And call recursively on each part of a multipart. */
1314 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1315 reverse_alternative_parts (part
->mp_part
);
1329 CI ci
= &ct
->c_ctinfo
;
1331 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1333 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1334 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1338 /* check for missing subtype */
1339 if (!*ci
->ci_subtype
)
1340 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1343 for (kv
= SubMessage
; kv
->kv_key
; kv
++)
1344 if (!strcasecmp (ci
->ci_subtype
, kv
->kv_key
))
1346 ct
->c_subtype
= kv
->kv_value
;
1348 switch (ct
->c_subtype
) {
1349 case MESSAGE_RFC822
:
1352 case MESSAGE_PARTIAL
:
1357 if ((p
= (struct partial
*) calloc (1, sizeof(*p
))) == NULL
)
1358 adios (NULL
, "out of memory");
1359 ct
->c_ctparams
= (void *) p
;
1361 /* scan for parameters "id", "number", and "total" */
1362 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1363 if (!strcasecmp (pm
->pm_name
, "id")) {
1364 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1367 if (!strcasecmp (pm
->pm_name
, "number")) {
1368 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1369 || p
->pm_partno
< 1) {
1372 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1373 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1374 ct
->c_file
, TYPE_FIELD
);
1379 if (!strcasecmp (pm
->pm_name
, "total")) {
1380 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1389 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1391 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1392 ci
->ci_type
, ci
->ci_subtype
,
1393 ct
->c_file
, TYPE_FIELD
);
1399 case MESSAGE_EXTERNAL
:
1406 if ((e
= (struct exbody
*) calloc (1, sizeof(*e
))) == NULL
)
1407 adios (NULL
, "out of memory");
1408 ct
->c_ctparams
= (void *) e
;
1411 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1412 advise (ct
->c_file
, "unable to open for reading");
1416 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1418 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1426 p
->c_ceopenfnx
= NULL
;
1427 if ((exresult
= params_external (ct
, 0)) != NOTOK
1428 && p
->c_ceopenfnx
== openMail
) {
1432 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1434 content_error (NULL
, ct
,
1435 "empty body for access-type=mail-server");
1439 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1440 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1442 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1444 adios ("failed", "fread");
1447 adios (NULL
, "unexpected EOF from fread");
1450 bp
+= cc
, size
-= cc
;
1457 p
->c_end
= p
->c_begin
;
1462 if (exresult
== NOTOK
)
1464 if (e
->eb_flags
== NOTOK
)
1467 switch (p
->c_type
) {
1472 if (p
->c_subtype
!= MESSAGE_RFC822
)
1476 e
->eb_partno
= ct
->c_partno
;
1478 (*p
->c_ctinitfnx
) (p
);
1493 params_external (CT ct
, int composing
)
1496 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1497 CI ci
= &ct
->c_ctinfo
;
1499 ct
->c_ceopenfnx
= NULL
;
1500 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1501 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1502 struct str2init
*s2i
;
1503 CT p
= e
->eb_content
;
1505 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1506 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1509 e
->eb_access
= pm
->pm_value
;
1510 e
->eb_flags
= NOTOK
;
1511 p
->c_encoding
= CE_EXTERNAL
;
1514 e
->eb_access
= s2i
->si_key
;
1515 e
->eb_flags
= s2i
->si_val
;
1516 p
->c_encoding
= CE_EXTERNAL
;
1518 /* Call the Init function for this external type */
1519 if ((*s2i
->si_init
)(p
) == NOTOK
)
1523 if (!strcasecmp (pm
->pm_name
, "name")) {
1524 e
->eb_name
= pm
->pm_value
;
1527 if (!strcasecmp (pm
->pm_name
, "permission")) {
1528 e
->eb_permission
= pm
->pm_value
;
1531 if (!strcasecmp (pm
->pm_name
, "site")) {
1532 e
->eb_site
= pm
->pm_value
;
1535 if (!strcasecmp (pm
->pm_name
, "directory")) {
1536 e
->eb_dir
= pm
->pm_value
;
1539 if (!strcasecmp (pm
->pm_name
, "mode")) {
1540 e
->eb_mode
= pm
->pm_value
;
1543 if (!strcasecmp (pm
->pm_name
, "size")) {
1544 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1547 if (!strcasecmp (pm
->pm_name
, "server")) {
1548 e
->eb_server
= pm
->pm_value
;
1551 if (!strcasecmp (pm
->pm_name
, "subject")) {
1552 e
->eb_subject
= pm
->pm_value
;
1555 if (!strcasecmp (pm
->pm_name
, "url")) {
1557 * According to RFC 2017, we have to remove all whitespace from
1561 char *u
, *p
= pm
->pm_value
;
1562 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1564 for (; *p
!= '\0'; p
++) {
1565 if (! isspace((unsigned char) *p
))
1572 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1573 e
->eb_body
= getcpy (pm
->pm_value
);
1578 if (!e
->eb_access
) {
1580 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1581 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1594 InitApplication (CT ct
)
1597 CI ci
= &ct
->c_ctinfo
;
1600 for (kv
= SubApplication
; kv
->kv_key
; kv
++)
1601 if (!strcasecmp (ci
->ci_subtype
, kv
->kv_key
))
1603 ct
->c_subtype
= kv
->kv_value
;
1610 * TRANSFER ENCODINGS
1614 init_encoding (CT ct
, OpenCEFunc openfnx
)
1616 ct
->c_ceopenfnx
= openfnx
;
1617 ct
->c_ceclosefnx
= close_encoding
;
1618 ct
->c_cesizefnx
= size_encoding
;
1625 close_encoding (CT ct
)
1627 CE ce
= &ct
->c_cefile
;
1636 static unsigned long
1637 size_encoding (CT ct
)
1642 CE ce
= &ct
->c_cefile
;
1645 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1646 return (long) st
.st_size
;
1649 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1650 return (long) st
.st_size
;
1655 if (ct
->c_encoding
== CE_EXTERNAL
)
1656 return (ct
->c_end
- ct
->c_begin
);
1659 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1660 return (ct
->c_end
- ct
->c_begin
);
1662 if (fstat (fd
, &st
) != NOTOK
)
1663 size
= (long) st
.st_size
;
1667 (*ct
->c_ceclosefnx
) (ct
);
1676 static unsigned char b642nib
[0x80] = {
1677 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1678 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1679 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1680 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1681 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1682 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1683 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1684 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1685 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
1686 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1687 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1688 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1689 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1690 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1691 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1692 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1699 return init_encoding (ct
, openBase64
);
1704 openBase64 (CT ct
, char **file
)
1706 int bitno
, cc
, digested
;
1707 int fd
, len
, skip
, own_ct_fp
= 0, text
= ct
->c_type
== CT_TEXT
;
1709 unsigned char value
, b
;
1710 char *cp
, *ep
, buffer
[BUFSIZ
];
1711 /* sbeck -- handle suffixes */
1713 CE ce
= &ct
->c_cefile
;
1717 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1722 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1723 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1729 if (*file
== NULL
) {
1732 ce
->ce_file
= add (*file
, NULL
);
1736 /* sbeck@cise.ufl.edu -- handle suffixes */
1738 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1739 if (ce
->ce_unlink
) {
1740 /* Create temporary file with filename extension. */
1741 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1742 adios(NULL
, "unable to create temporary file in %s",
1746 ce
->ce_file
= add (cp
, ce
->ce_file
);
1748 } else if (*file
== NULL
) {
1750 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1751 adios(NULL
, "unable to create temporary file in %s",
1754 ce
->ce_file
= add (tempfile
, NULL
);
1757 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1758 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1762 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1763 adios (NULL
, "internal error(1)");
1766 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1767 content_error (ct
->c_file
, ct
, "unable to open for reading");
1773 if ((digested
= ct
->c_digested
))
1774 MD5Init (&mdContext
);
1780 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1782 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
1784 content_error (ct
->c_file
, ct
, "error reading from");
1788 content_error (NULL
, ct
, "premature eof");
1796 for (ep
= (cp
= buffer
) + cc
; cp
< ep
; cp
++) {
1799 if (isspace ((unsigned char) *cp
))
1801 if (skip
|| (((unsigned char) *cp
) & 0x80)
1802 || (value
= b642nib
[((unsigned char) *cp
) & 0x7f]) > 0x3f) {
1804 fprintf (stderr
, "*cp=0x%x pos=%ld skip=%d\n",
1805 (unsigned char) *cp
,
1806 (long) (lseek (fd
, (off_t
) 0, SEEK_CUR
) - (ep
- cp
)),
1809 content_error (NULL
, ct
,
1810 "invalid BASE64 encoding -- continuing");
1814 bits
|= value
<< bitno
;
1816 if ((bitno
-= 6) < 0) {
1817 b
= (bits
>> 16) & 0xff;
1818 if (!text
|| b
!= '\r')
1819 putc ((char) b
, ce
->ce_fp
);
1821 MD5Update (&mdContext
, &b
, 1);
1823 b
= (bits
>> 8) & 0xff;
1824 if (! text
|| b
!= '\r')
1825 putc ((char) b
, ce
->ce_fp
);
1827 MD5Update (&mdContext
, &b
, 1);
1830 if (! text
|| b
!= '\r')
1831 putc ((char) b
, ce
->ce_fp
);
1833 MD5Update (&mdContext
, &b
, 1);
1837 if (ferror (ce
->ce_fp
)) {
1838 content_error (ce
->ce_file
, ct
,
1839 "error writing to");
1842 bitno
= 18, bits
= 0L, skip
= 0;
1848 goto self_delimiting
;
1857 fprintf (stderr
, "premature ending (bitno %d)\n", bitno
);
1859 content_error (NULL
, ct
, "invalid BASE64 encoding");
1864 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1866 if (fflush (ce
->ce_fp
)) {
1867 content_error (ce
->ce_file
, ct
, "error writing to");
1872 unsigned char digest
[16];
1874 MD5Final (digest
, &mdContext
);
1875 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
1876 sizeof(digest
) / sizeof(digest
[0])))
1877 content_error (NULL
, ct
,
1878 "content integrity suspect (digest mismatch) -- continuing");
1881 fprintf (stderr
, "content integrity confirmed\n");
1884 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1887 *file
= ce
->ce_file
;
1892 return fileno (ce
->ce_fp
);
1899 free_encoding (ct
, 0);
1908 static char hex2nib
[0x80] = {
1909 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1914 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1915 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1916 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1917 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1918 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1919 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1920 0x00, 0x00, 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
1931 return init_encoding (ct
, openQuoted
);
1936 openQuoted (CT ct
, char **file
)
1938 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1944 CE ce
= &ct
->c_cefile
;
1945 /* sbeck -- handle suffixes */
1950 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1955 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1956 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1962 if (*file
== NULL
) {
1965 ce
->ce_file
= add (*file
, NULL
);
1969 /* sbeck@cise.ufl.edu -- handle suffixes */
1971 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1972 if (ce
->ce_unlink
) {
1973 /* Create temporary file with filename extension. */
1974 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1975 adios(NULL
, "unable to create temporary file in %s",
1979 ce
->ce_file
= add (cp
, ce
->ce_file
);
1981 } else if (*file
== NULL
) {
1983 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1984 adios(NULL
, "unable to create temporary file in %s",
1987 ce
->ce_file
= add (tempfile
, NULL
);
1990 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1991 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1995 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1996 adios (NULL
, "internal error(2)");
1999 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2000 content_error (ct
->c_file
, ct
, "unable to open for reading");
2006 if ((digested
= ct
->c_digested
))
2007 MD5Init (&mdContext
);
2014 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2016 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2017 content_error (NULL
, ct
, "premature eof");
2021 if ((cc
= gotlen
) > len
)
2025 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2026 if (!isspace ((unsigned char) *ep
))
2030 for (; cp
< ep
; cp
++) {
2032 /* in an escape sequence */
2034 /* at byte 1 of an escape sequence */
2035 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2036 /* next is byte 2 */
2039 /* at byte 2 of an escape sequence */
2041 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2042 putc (mask
, ce
->ce_fp
);
2044 MD5Update (&mdContext
, &mask
, 1);
2045 if (ferror (ce
->ce_fp
)) {
2046 content_error (ce
->ce_file
, ct
, "error writing to");
2049 /* finished escape sequence; next may be literal or a new
2050 * escape sequence */
2053 /* on to next byte */
2057 /* not in an escape sequence */
2059 /* starting an escape sequence, or invalid '='? */
2060 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2061 /* "=\n" soft line break, eat the \n */
2065 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2066 /* We don't have 2 bytes left, so this is an invalid
2067 * escape sequence; just show the raw bytes (below). */
2068 } else if (isxdigit ((unsigned char) cp
[1]) &&
2069 isxdigit ((unsigned char) cp
[2])) {
2070 /* Next 2 bytes are hex digits, making this a valid escape
2071 * sequence; let's decode it (above). */
2075 /* One or both of the next 2 is out of range, making this
2076 * an invalid escape sequence; just show the raw bytes
2081 /* Just show the raw byte. */
2082 putc (*cp
, ce
->ce_fp
);
2085 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2087 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2090 if (ferror (ce
->ce_fp
)) {
2091 content_error (ce
->ce_file
, ct
, "error writing to");
2097 content_error (NULL
, ct
,
2098 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2102 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2104 if (fflush (ce
->ce_fp
)) {
2105 content_error (ce
->ce_file
, ct
, "error writing to");
2110 unsigned char digest
[16];
2112 MD5Final (digest
, &mdContext
);
2113 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2114 sizeof(digest
) / sizeof(digest
[0])))
2115 content_error (NULL
, ct
,
2116 "content integrity suspect (digest mismatch) -- continuing");
2119 fprintf (stderr
, "content integrity confirmed\n");
2122 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2125 *file
= ce
->ce_file
;
2130 return fileno (ce
->ce_fp
);
2133 free_encoding (ct
, 0);
2149 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2152 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2158 open7Bit (CT ct
, char **file
)
2160 int cc
, fd
, len
, own_ct_fp
= 0;
2161 char buffer
[BUFSIZ
];
2162 /* sbeck -- handle suffixes */
2165 CE ce
= &ct
->c_cefile
;
2168 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2173 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2174 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2180 if (*file
== NULL
) {
2183 ce
->ce_file
= add (*file
, NULL
);
2187 /* sbeck@cise.ufl.edu -- handle suffixes */
2189 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2190 if (ce
->ce_unlink
) {
2191 /* Create temporary file with filename extension. */
2192 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2193 adios(NULL
, "unable to create temporary file in %s",
2197 ce
->ce_file
= add (cp
, ce
->ce_file
);
2199 } else if (*file
== NULL
) {
2201 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2202 adios(NULL
, "unable to create temporary file in %s",
2205 ce
->ce_file
= add (tempfile
, NULL
);
2208 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2209 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2213 if (ct
->c_type
== CT_MULTIPART
) {
2214 CI ci
= &ct
->c_ctinfo
;
2218 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2219 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2220 + 1 + strlen (ci
->ci_subtype
);
2221 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2224 fputs (buffer
, ce
->ce_fp
);
2228 if (ci
->ci_comment
) {
2229 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2230 fputs ("\n\t", ce
->ce_fp
);
2234 putc (' ', ce
->ce_fp
);
2237 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2240 fprintf (ce
->ce_fp
, "\n");
2242 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2244 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2246 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2247 fprintf (ce
->ce_fp
, "\n");
2250 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2251 adios (NULL
, "internal error(3)");
2254 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2255 content_error (ct
->c_file
, ct
, "unable to open for reading");
2261 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2263 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2265 content_error (ct
->c_file
, ct
, "error reading from");
2269 content_error (NULL
, ct
, "premature eof");
2277 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2278 advise ("open7Bit", "fwrite");
2280 if (ferror (ce
->ce_fp
)) {
2281 content_error (ce
->ce_file
, ct
, "error writing to");
2286 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2288 if (fflush (ce
->ce_fp
)) {
2289 content_error (ce
->ce_file
, ct
, "error writing to");
2293 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2296 *file
= ce
->ce_file
;
2301 return fileno (ce
->ce_fp
);
2304 free_encoding (ct
, 0);
2318 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2320 char cachefile
[BUFSIZ
];
2323 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2328 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2329 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2335 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2336 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2337 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2338 ce
->ce_file
= getcpy (cachefile
);
2342 admonish (cachefile
, "unable to fopen for reading");
2346 *fd
= fileno (ce
->ce_fp
);
2350 *file
= ce
->ce_file
;
2351 *fd
= fileno (ce
->ce_fp
);
2362 return init_encoding (ct
, openFile
);
2367 openFile (CT ct
, char **file
)
2370 char cachefile
[BUFSIZ
];
2371 struct exbody
*e
= ct
->c_ctexbody
;
2372 CE ce
= &ct
->c_cefile
;
2374 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2386 content_error (NULL
, ct
, "missing name parameter");
2390 ce
->ce_file
= getcpy (e
->eb_name
);
2393 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2394 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2398 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2399 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2400 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2404 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2405 if ((fp
= fopen (cachefile
, "w"))) {
2407 char buffer
[BUFSIZ
];
2408 FILE *gp
= ce
->ce_fp
;
2410 fseek (gp
, 0L, SEEK_SET
);
2412 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2414 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2415 advise ("openFile", "fwrite");
2420 admonish (ce
->ce_file
, "error reading");
2421 (void) m_unlink (cachefile
);
2425 admonish (cachefile
, "error writing");
2426 (void) m_unlink (cachefile
);
2433 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2434 *file
= ce
->ce_file
;
2435 return fileno (ce
->ce_fp
);
2445 return init_encoding (ct
, openFTP
);
2450 openFTP (CT ct
, char **file
)
2452 int cachetype
, caching
, fd
;
2454 char *bp
, *ftp
, *user
, *pass
;
2455 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2457 CE ce
= &ct
->c_cefile
;
2458 static char *username
= NULL
;
2459 static char *password
= NULL
;
2463 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2469 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2480 if (!e
->eb_name
|| !e
->eb_site
) {
2481 content_error (NULL
, ct
, "missing %s parameter",
2482 e
->eb_name
? "site": "name");
2486 /* Get the buffer ready to go */
2488 buflen
= sizeof(buffer
);
2491 * Construct the query message for user
2493 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2499 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2505 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2506 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2511 if (e
->eb_size
> 0) {
2512 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2517 snprintf (bp
, buflen
, "? ");
2520 * Now, check the answer
2522 if (!getanswer (buffer
))
2527 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2531 ruserpass (e
->eb_site
, &username
, &password
);
2536 ce
->ce_unlink
= (*file
== NULL
);
2538 cachefile
[0] = '\0';
2539 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2540 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2541 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2542 if (*file
== NULL
) {
2549 ce
->ce_file
= add (*file
, NULL
);
2551 ce
->ce_file
= add (cachefile
, NULL
);
2554 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2555 adios(NULL
, "unable to create temporary file in %s",
2558 ce
->ce_file
= add (tempfile
, NULL
);
2561 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2562 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2567 int child_id
, i
, vecp
;
2571 vec
[vecp
++] = r1bindex (ftp
, '/');
2572 vec
[vecp
++] = e
->eb_site
;
2575 vec
[vecp
++] = e
->eb_dir
;
2576 vec
[vecp
++] = e
->eb_name
;
2577 vec
[vecp
++] = ce
->ce_file
,
2578 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2579 ? "ascii" : "binary";
2584 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2588 adios ("fork", "unable to");
2592 close (fileno (ce
->ce_fp
));
2594 fprintf (stderr
, "unable to exec ");
2600 if (pidXwait (child_id
, NULL
)) {
2601 username
= password
= NULL
;
2611 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2616 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2617 if ((fp
= fopen (cachefile
, "w"))) {
2619 FILE *gp
= ce
->ce_fp
;
2621 fseek (gp
, 0L, SEEK_SET
);
2623 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2625 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2626 advise ("openFTP", "fwrite");
2631 admonish (ce
->ce_file
, "error reading");
2632 (void) m_unlink (cachefile
);
2636 admonish (cachefile
, "error writing");
2637 (void) m_unlink (cachefile
);
2645 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2646 *file
= ce
->ce_file
;
2647 return fileno (ce
->ce_fp
);
2658 return init_encoding (ct
, openMail
);
2663 openMail (CT ct
, char **file
)
2665 int child_id
, fd
, i
, vecp
;
2667 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2668 struct exbody
*e
= ct
->c_ctexbody
;
2669 CE ce
= &ct
->c_cefile
;
2671 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2682 if (!e
->eb_server
) {
2683 content_error (NULL
, ct
, "missing server parameter");
2687 /* Get buffer ready to go */
2689 buflen
= sizeof(buffer
);
2691 /* Now, construct query message */
2692 snprintf (bp
, buflen
, "Retrieve content");
2698 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2704 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2706 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2708 /* Now, check answer */
2709 if (!getanswer (buffer
))
2713 vec
[vecp
++] = r1bindex (mailproc
, '/');
2714 vec
[vecp
++] = e
->eb_server
;
2715 vec
[vecp
++] = "-subject";
2716 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2717 vec
[vecp
++] = "-body";
2718 vec
[vecp
++] = e
->eb_body
;
2721 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2725 advise ("fork", "unable to");
2729 execvp (mailproc
, vec
);
2730 fprintf (stderr
, "unable to exec ");
2736 if (pidXwait (child_id
, NULL
) == OK
)
2737 advise (NULL
, "request sent");
2741 if (*file
== NULL
) {
2743 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2744 adios(NULL
, "unable to create temporary file in %s",
2747 ce
->ce_file
= add (tempfile
, NULL
);
2750 ce
->ce_file
= add (*file
, NULL
);
2754 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2755 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2759 /* showproc is for mhshow and mhstore, though mhlist -debug
2760 * prints it, too. */
2762 free (ct
->c_showproc
);
2763 ct
->c_showproc
= add ("true", NULL
);
2765 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2766 *file
= ce
->ce_file
;
2767 return fileno (ce
->ce_fp
);
2778 return init_encoding (ct
, openURL
);
2783 openURL (CT ct
, char **file
)
2785 struct exbody
*e
= ct
->c_ctexbody
;
2786 CE ce
= &ct
->c_cefile
;
2787 char *urlprog
, *program
;
2788 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2789 int fd
, caching
, cachetype
;
2790 struct msgs_array args
= { 0, 0, NULL
};
2793 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2797 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2801 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2813 content_error(NULL
, ct
, "missing url parameter");
2817 ce
->ce_unlink
= (*file
== NULL
);
2819 cachefile
[0] = '\0';
2821 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2822 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2823 if (*file
== NULL
) {
2830 ce
->ce_file
= add(*file
, NULL
);
2832 ce
->ce_file
= add(cachefile
, NULL
);
2835 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2836 adios(NULL
, "unable to create temporary file in %s",
2839 ce
->ce_file
= add (tempfile
, NULL
);
2842 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2843 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2847 switch (child_id
= fork()) {
2849 adios ("fork", "unable to");
2853 argsplit_msgarg(&args
, urlprog
, &program
);
2854 app_msgarg(&args
, e
->eb_url
);
2855 app_msgarg(&args
, NULL
);
2856 dup2(fileno(ce
->ce_fp
), 1);
2857 close(fileno(ce
->ce_fp
));
2858 execvp(program
, args
.msgs
);
2859 fprintf(stderr
, "Unable to exec ");
2865 if (pidXwait(child_id
, NULL
)) {
2873 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2878 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2879 if ((fp
= fopen(cachefile
, "w"))) {
2881 FILE *gp
= ce
->ce_fp
;
2883 fseeko(gp
, 0, SEEK_SET
);
2885 while ((cc
= fread(buffer
, sizeof(*buffer
),
2886 sizeof(buffer
), gp
)) > 0)
2887 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2888 advise ("openURL", "fwrite");
2894 admonish(ce
->ce_file
, "error reading");
2895 (void) m_unlink (cachefile
);
2902 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2903 *file
= ce
->ce_file
;
2908 readDigest (CT ct
, char *cp
)
2913 unsigned char *dp
, value
, *ep
;
2919 for (ep
= (dp
= ct
->c_digest
)
2920 + sizeof(ct
->c_digest
) / sizeof(ct
->c_digest
[0]); *cp
; cp
++)
2925 || (value
= b642nib
[*cp
& 0x7f]) > 0x3f) {
2927 fprintf (stderr
, "invalid BASE64 encoding\n");
2931 bits
|= value
<< bitno
;
2933 if ((bitno
-= 6) < 0) {
2934 if (dp
+ (3 - skip
) > ep
)
2935 goto invalid_digest
;
2936 *dp
++ = (bits
>> 16) & 0xff;
2938 *dp
++ = (bits
>> 8) & 0xff;
2940 *dp
++ = bits
& 0xff;
2950 goto self_delimiting
;
2955 fprintf (stderr
, "premature ending (bitno %d)\n", bitno
);
2965 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2973 fprintf (stderr
, "MD5 digest=");
2974 for (dp
= ct
->c_digest
; dp
< ep
; dp
++)
2975 fprintf (stderr
, "%02x", *dp
& 0xff);
2976 fprintf (stderr
, "\n");
2983 /* Multipart parts might have content before the first subpart and/or
2984 after the last subpart that hasn't been stored anywhere else, so do
2987 get_leftover_mp_content (CT ct
, int before
/* or after */)
2989 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2991 int found_boundary
= 0;
2997 char *content
= NULL
;
2999 if (! m
) return NOTOK
;
3002 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
3004 /* Isolate the beginning of this part to the beginning of the
3005 first subpart and save any content between them. */
3006 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
3007 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
3008 boundary
= concat ("--", m
->mp_start
, NULL
);
3010 struct part
*last_subpart
= NULL
;
3011 struct part
*subpart
;
3013 /* Go to the last subpart to get its end position. */
3014 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
3015 last_subpart
= subpart
;
3018 if (last_subpart
== NULL
) return NOTOK
;
3020 /* Isolate the end of the last subpart to the end of this part
3021 and save any content between them. */
3022 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
3023 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
3024 boundary
= concat ("--", m
->mp_stop
, NULL
);
3027 /* Back up by 1 to pick up the newline. */
3028 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
3030 /* Don't look beyond beginning of first subpart (before) or
3031 next part (after). */
3032 if (read
> max
) bufp
[read
-max
] = '\0';
3035 if (! strcmp (bufp
, boundary
)) {
3039 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3045 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3047 char *old_content
= content
;
3048 content
= concat (content
, bufp
, NULL
);
3052 ? concat ("\n", bufp
, NULL
)
3053 : concat (bufp
, NULL
);
3058 if (found_boundary
|| read
> max
) break;
3060 if (read
> max
) break;
3064 /* Skip the newline if that's all there is. */
3068 /* Remove trailing newline, except at EOF. */
3069 if ((before
|| ! feof (ct
->c_fp
)) &&
3070 (cp
= content
+ strlen (content
)) > content
&&
3075 if (strlen (content
) > 1) {
3077 m
->mp_content_before
= content
;
3079 m
->mp_content_after
= content
;
3094 ct_type_str (int type
) {
3096 case CT_APPLICATION
:
3097 return "application";
3113 return "unknown_type";
3119 ct_subtype_str (int type
, int subtype
) {
3121 case CT_APPLICATION
:
3123 case APPLICATION_OCTETS
:
3125 case APPLICATION_POSTSCRIPT
:
3126 return "postscript";
3128 return "unknown_app_subtype";
3132 case MESSAGE_RFC822
:
3134 case MESSAGE_PARTIAL
:
3136 case MESSAGE_EXTERNAL
:
3139 return "unknown_msg_subtype";
3145 case MULTI_ALTERNATE
:
3146 return "alternative";
3149 case MULTI_PARALLEL
:
3152 return "unknown_multipart_subtype";
3163 return "unknown_text_subtype";
3166 return "unknown_type";
3171 /* Find the content type and InitFunc for the CT. */
3172 const struct str2init
*
3173 get_ct_init (int type
) {
3174 const struct str2init
*sp
;
3176 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3177 if (type
== sp
->si_val
) {
3186 ce_str (int encoding
) {
3191 return "quoted-printable";
3207 /* Find the content type and InitFunc for the content encoding method. */
3208 const struct str2init
*
3209 get_ce_method (const char *method
) {
3210 struct str2init
*sp
;
3212 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3213 if (! strcasecmp (method
, sp
->si_key
)) {
3222 * Parse a series of MIME attributes (or parameters) given a header as
3225 * Arguments include:
3227 * filename - Name of input file (for error messages)
3228 * fieldname - Name of field being processed
3229 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3230 * Updated to point to end of attributes when finished.
3231 * param_head - Pointer to head of parameter list
3232 * param_tail - Pointer to tail of parameter list
3233 * commentp - Pointer to header comment pointer (may be NULL)
3235 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3236 * DONE to indicate a benign error (minor parsing error, but the program
3241 parse_header_attrs (const char *filename
, const char *fieldname
,
3242 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3245 char *cp
= *header_attrp
;
3251 struct sectlist
*next
;
3257 struct sectlist
*sechead
;
3258 struct parmlist
*next
;
3259 } *pp
, *pp2
, *phead
= NULL
;
3261 while (*cp
== ';') {
3262 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3263 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3266 while (isspace ((unsigned char) *cp
))
3270 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3276 "extraneous trailing ';' in message %s's %s: "
3278 filename
, fieldname
);
3282 /* down case the attribute name */
3283 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3284 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
3285 *dp
= tolower ((unsigned char) *dp
);
3287 for (up
= dp
; isspace ((unsigned char) *dp
);)
3289 if (dp
== cp
|| *dp
!= '=') {
3291 "invalid parameter in message %s's %s: "
3292 "field\n%*sparameter %s (error detected at offset %d)",
3293 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3298 * To handle RFC 2231, we have to deal with the following extensions:
3300 * name*=encoded-value
3301 * name*<N>=part-N-of-a-parameter-value
3302 * name*<N>*=encoded-part-N-of-a-parameter-value
3305 * If there's a * right before the equal sign, it's encoded.
3306 * If there's a * and one or more digits, then it's section N.
3308 * Remember we can have one or the other, or both. cp points to
3309 * beginning of name, up points past the last character in the
3313 for (vp
= cp
; vp
< up
; vp
++) {
3314 if (*vp
== '*' && vp
< up
- 1) {
3317 } else if (*vp
== '*' && vp
== up
- 1) {
3319 } else if (partial
) {
3320 if (isdigit((unsigned char) *vp
))
3321 index
= *vp
- '0' + index
* 10;
3323 advise (NULL
, "invalid parameter index in message %s's "
3324 "%s: field\n%*s(parameter %s)", filename
,
3325 fieldname
, strlen(invo_name
) + 2, "", cp
);
3334 * Break out the parameter name and value sections and allocate
3338 nameptr
= mh_xmalloc(len
+ 1);
3339 strncpy(nameptr
, cp
, len
);
3340 nameptr
[len
] = '\0';
3342 for (dp
++; isspace ((unsigned char) *dp
);)
3347 * Single quotes delimit the character set and language tag.
3348 * They are required on the first section (or a complete
3353 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3359 charset
= mh_xmalloc(len
+ 1);
3360 strncpy(charset
, dp
, len
);
3361 charset
[len
] = '\0';
3367 advise(NULL
, "missing charset in message %s's %s: "
3368 "field\n%*s(parameter %s)", filename
, fieldname
,
3369 strlen(invo_name
) + 2, "", nameptr
);
3375 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3382 lang
= mh_xmalloc(len
+ 1);
3383 strncpy(lang
, dp
, len
);
3390 advise(NULL
, "missing language tag in message %s's %s: "
3391 "field\n%*s(parameter %s)", filename
, fieldname
,
3392 strlen(invo_name
) + 2, "", nameptr
);
3403 * At this point vp should be pointing at the beginning
3404 * of the encoded value/section. Continue until we reach
3405 * the end or get whitespace. But first, calculate the
3406 * length so we can allocate the correct buffer size.
3409 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3411 if (*(vp
+ 1) == '\0' ||
3412 !isxdigit((unsigned char) *(vp
+ 1)) ||
3413 *(vp
+ 2) == '\0' ||
3414 !isxdigit((unsigned char) *(vp
+ 2))) {
3415 advise(NULL
, "invalid encoded sequence in message "
3416 "%s's %s: field\n%*s(parameter %s)",
3417 filename
, fieldname
, strlen(invo_name
) + 2,
3431 up
= valptr
= mh_xmalloc(len
+ 1);
3433 for (vp
= dp
; istoken(*vp
); vp
++) {
3435 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3446 * A "normal" string. If it's got a leading quote, then we
3447 * strip the quotes out. Otherwise go until we reach the end
3448 * or get whitespace. Note we scan it twice; once to get the
3449 * length, then the second time copies it into the destination
3456 for (cp
= dp
+ 1;;) {
3461 "invalid quoted-string in message %s's %s: "
3462 "field\n%*s(parameter %s)",
3463 filename
, fieldname
, strlen(invo_name
) + 2, "",
3486 for (cp
= dp
; istoken (*cp
); cp
++) {
3491 valptr
= mh_xmalloc(len
+ 1);
3495 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3503 strncpy(valptr
, cp
= dp
, len
);
3511 * If 'partial' is set, we don't allocate a parameter now. We
3512 * put it on the parameter linked list to be reassembled later.
3514 * "phead" points to a list of all parameters we need to reassemble.
3515 * Each parameter has a list of sections. We insert the sections in
3520 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3521 if (strcasecmp(nameptr
, pp
->name
) == 0)
3526 pp
= mh_xmalloc(sizeof(*pp
));
3527 memset(pp
, 0, sizeof(*pp
));
3534 * Insert this into the section linked list
3537 sp
= mh_xmalloc(sizeof(*sp
));
3538 memset(sp
, 0, sizeof(*sp
));
3543 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3544 sp
->next
= pp
->sechead
;
3547 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3548 if (sp2
->index
== sp
->index
) {
3549 advise (NULL
, "duplicate index (%d) in message "
3550 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3551 filename
, fieldname
, strlen(invo_name
) + 2, "",
3555 if (sp2
->index
< sp
->index
&&
3556 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3557 sp
->next
= sp2
->next
;
3564 advise(NULL
, "Internal error: cannot insert partial "
3565 "param in message %s's %s: field\n%*s(parameter %s)",
3566 filename
, fieldname
, strlen(invo_name
) + 2, "",
3573 * Save our charset and lang tags.
3576 if (index
== 0 && encoded
) {
3579 pp
->charset
= charset
;
3585 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3586 pm
->pm_charset
= charset
;
3590 while (isspace ((unsigned char) *cp
))
3594 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3600 * Now that we're done, reassemble all of the partial parameters.
3603 for (pp
= phead
; pp
!= NULL
; ) {
3607 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3608 if (sp
->index
!= pindex
++) {
3609 advise(NULL
, "missing section %d for parameter in "
3610 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3611 filename
, fieldname
, strlen(invo_name
) + 2, "",
3618 p
= q
= mh_xmalloc(tlen
+ 1);
3619 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3620 memcpy(q
, sp
->value
, sp
->len
);
3630 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3631 pm
->pm_charset
= pp
->charset
;
3632 pm
->pm_lang
= pp
->lang
;
3643 * Return the charset for a particular content type. Return pointer is
3644 * only valid until the next call to content_charset().
3648 content_charset (CT ct
) {
3649 static char *ret_charset
= NULL
;
3651 if (ret_charset
!= NULL
) {
3655 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3657 return ret_charset
? ret_charset
: "US-ASCII";
3662 * Create a string based on a list of output parameters. Assume that this
3663 * parameter string will be appended to an existing header, so start out
3664 * with the separator (;). Perform RFC 2231 encoding when necessary.
3668 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3670 char *paramout
= NULL
;
3671 char line
[CPERLIN
* 2], *q
;
3672 int curlen
, index
, cont
, encode
, i
;
3673 size_t valoff
, numchars
;
3675 while (params
!= NULL
) {
3681 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3684 if (strlen(params
->pm_name
) > CPERLIN
) {
3685 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3691 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3694 * Loop until we get a parameter that fits within a line. We
3695 * assume new lines start with a tab, so check our overflow based
3705 * At this point we're definitely continuing the line, so
3706 * be sure to include the parameter name and section index.
3709 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3710 params
->pm_name
, index
);
3713 * Both of these functions do a NUL termination
3717 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3718 numchars
, valoff
, index
);
3720 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3731 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3736 * "line" starts with a ;\n\t, so that doesn't count against
3737 * the length. But add 8 since it starts with a tab; that's
3738 * how we end up with 5.
3741 initialwidth
= strlen(line
) + 5;
3744 * At this point the line should be built, so add it to our
3745 * current output buffer.
3748 paramout
= add(line
, paramout
);
3752 * If this won't fit on the line, start a new one. Save room in
3753 * case we need a semicolon on the end
3756 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3768 * At this point, we're either finishing a contined parameter, or
3769 * we're working on a new one.
3773 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3774 params
->pm_name
, index
);
3776 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3781 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3782 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3784 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3785 strlen(params
->pm_value
+ valoff
), valoff
);
3793 paramout
= add(line
, paramout
);
3794 initialwidth
+= strlen(line
);
3796 params
= params
->pm_next
;
3800 *offsetout
= initialwidth
;
3806 * Calculate the size of a parameter.
3810 * pm - The parameter being output
3811 * index - If continuing the parameter, the index of the section
3813 * valueoff - The current offset into the parameter value that we're
3814 * working on (previous sections have consumed valueoff bytes).
3815 * encode - Set if we should perform encoding on this parameter section
3816 * (given that we're consuming bytesfit bytes).
3817 * cont - Set if the remaining data in value will not fit on a single
3818 * line and will need to be continued.
3819 * bytesfit - The number of bytes that we can consume from the parameter
3820 * value and still fit on a completely new line. The
3821 * calculation assumes the new line starts with a tab,
3822 * includes the parameter name and any encoding, and fits
3823 * within CPERLIN bytes. Will always be at least 1.
3827 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3830 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3831 size_t len
= 0, fit
= 0;
3832 int fitlimit
= 0, eightbit
, maxfit
;
3837 * Add up the length. First, start with the parameter name.
3840 len
= strlen(pm
->pm_name
);
3843 * Scan the parameter value and see if we need to do encoding for this
3847 eightbit
= contains8bit(start
, NULL
);
3850 * Determine if we need to encode this section. Encoding is necessary if:
3852 * - There are any 8-bit characters at all and we're on the first
3854 * - There are 8-bit characters within N bytes of our section start.
3855 * N is calculated based on the number of bytes it would take to
3856 * reach CPERLIN. Specifically:
3857 * 8 (starting tab) +
3858 * strlen(param name) +
3859 * 4 ('* for section marker, '=', opening/closing '"')
3861 * is the number of bytes used by everything that isn't part of the
3862 * value. So that gets subtracted from CPERLIN.
3865 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3866 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3867 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3871 len
++; /* Add in equal sign */
3875 * We're using maxfit as a marker for how many characters we can
3876 * fit into the line. Bump it by two because we're not using quotes
3883 * If we don't have a charset or language tag in this parameter,
3887 if (! pm
->pm_charset
) {
3888 pm
->pm_charset
= getcpy(write_charset_8bit());
3889 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3890 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3891 "local character set is US-ASCII", pm
->pm_name
);
3894 pm
->pm_lang
= getcpy(NULL
); /* Default to a blank lang tag */
3896 len
++; /* For the encoding marker */
3899 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3904 * We know we definitely need to include an index. maxfit already
3905 * includes the section marker.
3907 len
+= strlen(indexchar
);
3909 for (p
= start
; *p
!= '\0'; p
++) {
3910 if (isparamencode(*p
)) {
3918 * Just so there's no confusion: maxfit is counting OUTPUT
3919 * characters (post-encoding). fit is counting INPUT characters.
3921 if (! fitlimit
&& maxfit
>= 0)
3923 else if (! fitlimit
)
3928 * Calculate the string length, but add room for quoting \
3929 * and " if necessary. Also account for quotes at beginning
3932 for (p
= start
; *p
!= '\0'; p
++) {
3943 if (! fitlimit
&& maxfit
>= 0)
3945 else if (! fitlimit
)
3962 * Output an encoded parameter string.
3966 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3967 size_t valueoff
, int index
)
3969 size_t outlen
= 0, n
;
3970 char *endptr
= output
+ len
, *p
;
3973 * First, output the marker for an encoded string.
3981 * If the index is 0, output the character set and language tag.
3982 * If theses were NULL, they should have already been filled in
3987 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3991 if (output
> endptr
) {
3992 advise(NULL
, "Internal error: parameter buffer overflow");
3998 * Copy over the value, encoding if necessary
4001 p
= pm
->pm_value
+ valueoff
;
4002 while (valuelen
-- > 0) {
4003 if (isparamencode(*p
)) {
4004 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4011 if (output
> endptr
) {
4012 advise(NULL
, "Internal error: parameter buffer overflow");
4023 * Output a "normal" parameter, without encoding. Be sure to escape
4024 * quotes and backslashes if necessary.
4028 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4032 char *endptr
= output
+ len
, *p
;
4038 p
= pm
->pm_value
+ valueoff
;
4040 while (valuelen
-- > 0) {
4050 if (output
> endptr
) {
4051 advise(NULL
, "Internal error: parameter buffer overflow");
4056 if (output
- 2 > endptr
) {
4057 advise(NULL
, "Internal error: parameter buffer overflow");
4068 * Add a parameter to the parameter linked list
4072 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4074 PM pm
= mh_xmalloc(sizeof(*pm
));
4076 memset(pm
, 0, sizeof(*pm
));
4078 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4079 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4082 (*last
)->pm_next
= pm
;
4093 * Either replace a current parameter with a new value, or add the parameter
4094 * to the parameter linked list.
4098 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4102 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4103 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4105 * If nocopy is set, it's assumed that we own both name
4106 * and value. We don't need name, so we discard it now.
4111 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4116 return add_param(first
, last
, name
, value
, nocopy
);
4120 * Retrieve a parameter value from a parameter linked list. If the parameter
4121 * value needs converted to the local character set, do that now.
4125 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4127 while (first
!= NULL
) {
4128 if (strcasecmp(name
, first
->pm_name
) == 0) {
4130 return first
->pm_value
;
4132 return getcpy(get_param_value(first
, replace
));
4134 first
= first
->pm_next
;
4141 * Return a parameter value, converting to the local character set if
4145 char *get_param_value(PM pm
, char replace
)
4147 static char buffer
[4096]; /* I hope no parameters are larger */
4148 size_t bufsize
= sizeof(buffer
);
4153 ICONV_CONST
char *p
;
4154 #else /* HAVE_ICONV */
4156 #endif /* HAVE_ICONV */
4161 * If we don't have a character set indicated, it's assumed to be
4162 * US-ASCII. If it matches our character set, we don't need to convert
4166 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4167 strlen(pm
->pm_charset
))) {
4168 return pm
->pm_value
;
4172 * In this case, we need to convert. If we have iconv support, use
4173 * that. Otherwise, go through and simply replace every non-ASCII
4174 * character with the substitution character.
4179 bufsize
= sizeof(buffer
);
4180 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4182 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4183 if (cd
== (iconv_t
) -1) {
4187 inbytes
= strlen(pm
->pm_value
);
4191 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4192 if (errno
!= EILSEQ
) {
4197 * Reset shift state, substitute our character,
4198 * try to restart conversion.
4201 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4214 for (++p
, --inbytes
;
4215 inbytes
> 0 && (((unsigned char) *q
) & 0xc0) == 0x80;
4234 #endif /* HAVE_ICONV */
4237 * Take everything non-ASCII and substituite the replacement character
4241 bufsize
= sizeof(buffer
);
4242 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4243 if (isascii((unsigned char) *p
) && !iscntrl((unsigned char) *p
))