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 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
)
199 * Check if file is actually standard input
201 if ((is_stdin
= !(strcmp (file
, "-")))) {
202 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
204 advise("mhparse", "unable to create temporary file in %s",
208 file
= add (tfile
, NULL
);
210 while (fgets (buffer
, sizeof(buffer
), stdin
))
214 if (ferror (stdin
)) {
215 (void) m_unlink (file
);
216 advise ("stdin", "error reading");
220 (void) m_unlink (file
);
221 advise (file
, "error writing");
224 fseek (fp
, 0L, SEEK_SET
);
225 } else if ((fp
= fopen (file
, "r")) == NULL
) {
226 advise (file
, "unable to read");
230 if (!(ct
= get_content (fp
, file
, 1))) {
232 (void) m_unlink (file
);
233 advise (NULL
, "unable to decode %s", file
);
238 ct
->c_unlink
= 1; /* temp file to remove */
242 if (ct
->c_end
== 0L) {
243 fseek (fp
, 0L, SEEK_END
);
244 ct
->c_end
= ftell (fp
);
247 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
259 * Main routine for reading/parsing the headers
260 * of a message content.
262 * toplevel = 1 # we are at the top level of the message
263 * toplevel = 0 # we are inside message type or multipart type
264 * # other than multipart/digest
265 * toplevel = -1 # we are inside multipart/digest
266 * NB: on failure we will fclose(in)!
270 get_content (FILE *in
, char *file
, int toplevel
)
273 char buf
[BUFSIZ
], name
[NAMESZ
];
277 m_getfld_state_t gstate
= 0;
279 /* allocate the content structure */
280 if (!(ct
= (CT
) calloc (1, sizeof(*ct
))))
281 adios (NULL
, "out of memory");
284 ct
->c_file
= add (file
, NULL
);
285 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
288 * Parse the header fields for this
289 * content into a linked list.
291 m_getfld_track_filepos (&gstate
, in
);
292 for (compnum
= 1;;) {
293 int bufsz
= sizeof buf
;
294 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
299 /* get copies of the buffers */
300 np
= add (name
, NULL
);
301 vp
= add (buf
, NULL
);
303 /* if necessary, get rest of field */
304 while (state
== FLDPLUS
) {
306 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
307 vp
= add (buf
, vp
); /* add to previous value */
310 /* Now add the header data to the list */
311 add_header (ct
, np
, vp
);
313 /* continue, to see if this isn't the last header field */
314 ct
->c_begin
= ftell (in
) + 1;
318 ct
->c_begin
= ftell (in
) - strlen (buf
);
322 ct
->c_begin
= ftell (in
);
327 adios (NULL
, "message format error in component #%d", compnum
);
330 adios (NULL
, "getfld() returned %d", state
);
333 /* break out of the loop */
336 m_getfld_state_destroy (&gstate
);
339 * Read the content headers. We will parse the
340 * MIME related header fields into their various
341 * structures and set internal flags related to
342 * content type/subtype, etc.
345 hp
= ct
->c_first_hf
; /* start at first header field */
347 /* Get MIME-Version field */
348 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
353 advise (NULL
, "message %s has multiple %s: fields",
354 ct
->c_file
, VRSN_FIELD
);
357 ct
->c_vrsn
= add (hp
->value
, NULL
);
359 /* Now, cleanup this field */
362 while (isspace ((unsigned char) *cp
))
364 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
366 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
367 if (!isspace ((unsigned char) *dp
))
371 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
374 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
377 for (dp
= cp
; istoken (*dp
); dp
++)
381 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
384 admonish (NULL
, "message %s has unknown value for %s: field (%s)",
385 ct
->c_file
, VRSN_FIELD
, cp
);
388 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
389 /* Get Content-Type field */
390 struct str2init
*s2i
;
391 CI ci
= &ct
->c_ctinfo
;
393 /* Check if we've already seen a Content-Type header */
395 advise (NULL
, "message %s has multiple %s: fields",
396 ct
->c_file
, TYPE_FIELD
);
400 /* Parse the Content-Type field */
401 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
405 * Set the Init function and the internal
406 * flag for this content type.
408 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
409 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
411 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
413 ct
->c_type
= s2i
->si_val
;
414 ct
->c_ctinitfnx
= s2i
->si_init
;
416 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
417 /* Get Content-Transfer-Encoding field */
419 struct str2init
*s2i
;
422 * Check if we've already seen the
423 * Content-Transfer-Encoding field
426 advise (NULL
, "message %s has multiple %s: fields",
427 ct
->c_file
, ENCODING_FIELD
);
431 /* get copy of this field */
432 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
434 while (isspace ((unsigned char) *cp
))
436 for (dp
= cp
; istoken (*dp
); dp
++)
442 * Find the internal flag and Init function
443 * for this transfer encoding.
445 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
446 if (!strcasecmp (cp
, s2i
->si_key
))
448 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
451 ct
->c_encoding
= s2i
->si_val
;
453 /* Call the Init function for this encoding */
454 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
457 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
458 /* Get Content-MD5 field */
464 if (ct
->c_digested
) {
465 advise (NULL
, "message %s has multiple %s: fields",
466 ct
->c_file
, MD5_FIELD
);
470 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
472 while (isspace ((unsigned char) *cp
))
474 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
476 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
477 if (!isspace ((unsigned char) *dp
))
481 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
484 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
489 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
497 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
498 /* Get Content-ID field */
499 ct
->c_id
= add (hp
->value
, ct
->c_id
);
501 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
502 /* Get Content-Description field */
503 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
505 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
506 /* Get Content-Disposition field */
507 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
512 hp
= hp
->next
; /* next header field */
516 * Check if we saw a Content-Type field.
517 * If not, then assign a default value for
518 * it, and the Init function.
522 * If we are inside a multipart/digest message,
523 * so default type is message/rfc822
526 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
528 ct
->c_type
= CT_MESSAGE
;
529 ct
->c_ctinitfnx
= InitMessage
;
532 * Else default type is text/plain
534 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
536 ct
->c_type
= CT_TEXT
;
537 ct
->c_ctinitfnx
= InitText
;
541 /* Use default Transfer-Encoding, if necessary */
543 ct
->c_encoding
= CE_7BIT
;
556 * small routine to add header field to list
560 add_header (CT ct
, char *name
, char *value
)
564 /* allocate header field structure */
565 hp
= mh_xmalloc (sizeof(*hp
));
567 /* link data into header structure */
572 /* link header structure into the list */
573 if (ct
->c_first_hf
== NULL
) {
574 ct
->c_first_hf
= hp
; /* this is the first */
577 ct
->c_last_hf
->next
= hp
; /* add it to the end */
586 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
587 * directives. Fills in the information of the CTinfo structure.
590 get_ctinfo (char *cp
, CT ct
, int magic
)
599 /* store copy of Content-Type line */
600 cp
= ct
->c_ctline
= add (cp
, NULL
);
602 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
605 /* change newlines to spaces */
606 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
609 /* trim trailing spaces */
610 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
611 if (!isspace ((unsigned char) *dp
))
616 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
618 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
619 &ci
->ci_comment
) == NOTOK
)
622 for (dp
= cp
; istoken (*dp
); dp
++)
625 ci
->ci_type
= add (cp
, NULL
); /* store content type */
629 advise (NULL
, "invalid %s: field in message %s (empty type)",
630 TYPE_FIELD
, ct
->c_file
);
634 /* down case the content type string */
635 for (dp
= ci
->ci_type
; *dp
; dp
++)
636 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
637 *dp
= tolower ((unsigned char) *dp
);
639 while (isspace ((unsigned char) *cp
))
642 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
643 &ci
->ci_comment
) == NOTOK
)
648 ci
->ci_subtype
= add ("", NULL
);
653 while (isspace ((unsigned char) *cp
))
656 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
657 &ci
->ci_comment
) == NOTOK
)
660 for (dp
= cp
; istoken (*dp
); dp
++)
663 ci
->ci_subtype
= add (cp
, NULL
); /* store the content subtype */
666 if (!*ci
->ci_subtype
) {
668 "invalid %s: field in message %s (empty subtype for \"%s\")",
669 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
673 /* down case the content subtype string */
674 for (dp
= ci
->ci_subtype
; *dp
; dp
++)
675 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
676 *dp
= tolower ((unsigned char) *dp
);
679 while (isspace ((unsigned char) *cp
))
682 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
683 &ci
->ci_comment
) == NOTOK
)
686 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
687 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
688 &ci
->ci_comment
)) != OK
) {
689 return status
== NOTOK
? NOTOK
: OK
;
693 * Get any <Content-Id> given in buffer
695 if (magic
&& *cp
== '<') {
700 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
701 advise (NULL
, "invalid ID in message %s", ct
->c_file
);
707 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
713 while (isspace ((unsigned char) *cp
))
718 * Get any [Content-Description] given in buffer.
720 if (magic
&& *cp
== '[') {
722 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
726 advise (NULL
, "invalid description in message %s", ct
->c_file
);
734 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
740 while (isspace ((unsigned char) *cp
))
745 * Get any {Content-Disposition} given in buffer.
747 if (magic
&& *cp
== '{') {
749 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
753 advise (NULL
, "invalid disposition in message %s", ct
->c_file
);
761 if (get_dispo(cp
, ct
, 1) != OK
)
767 while (isspace ((unsigned char) *cp
))
772 * Get any extension directives (right now just the content transfer
773 * encoding, but maybe others) that we care about.
776 if (magic
&& *cp
== '*') {
778 * See if it's a CTE we match on
783 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
787 advise (NULL
, "invalid null transfer encoding specification");
794 ct
->c_reqencoding
= CE_UNKNOWN
;
796 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
797 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
798 ct
->c_reqencoding
= kv
->kv_value
;
803 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
804 advise (NULL
, "invalid CTE specification: \"%s\"", dp
);
808 while (isspace ((unsigned char) *cp
))
813 * Check if anything is left over
817 ci
->ci_magic
= add (cp
, NULL
);
819 /* If there is a Content-Disposition header and it doesn't
820 have a *filename=, extract it from the magic contents.
821 The r1bindex call skips any leading directory
823 if (ct
->c_dispo_type
&&
824 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
825 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
826 r1bindex(ci
->ci_magic
, '/'), 0);
831 "extraneous information in message %s's %s: field\n%*s(%s)",
832 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
840 * Parse out a Content-Disposition header. A lot of this is cribbed from
844 get_dispo (char *cp
, CT ct
, int buildflag
)
846 char *dp
, *dispoheader
;
851 * Save the whole copy of the Content-Disposition header, unless we're
852 * processing a mhbuild directive. A NULL c_dispo will be a flag to
853 * mhbuild that the disposition header needs to be generated at that
857 dispoheader
= cp
= add(cp
, NULL
);
859 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
862 /* change newlines to spaces */
863 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
866 /* trim trailing spaces */
867 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
868 if (!isspace ((unsigned char) *dp
))
873 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
875 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
881 for (dp
= cp
; istoken (*dp
); dp
++)
884 ct
->c_dispo_type
= add (cp
, NULL
); /* store disposition type */
887 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
890 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
891 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
893 if (status
== NOTOK
) {
899 "extraneous information in message %s's %s: field\n%*s(%s)",
900 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
906 ct
->c_dispo
= dispoheader
;
913 get_comment (const char *filename
, const char *fieldname
, char **ap
,
918 char c
, buffer
[BUFSIZ
], *dp
;
928 advise (NULL
, "invalid comment in message %s's %s: field",
929 filename
, fieldname
);
934 if ((c
= *cp
++) == '\0')
957 if ((dp
= *commentp
)) {
958 *commentp
= concat (dp
, " ", buffer
, NULL
);
961 *commentp
= add (buffer
, NULL
);
965 while (isspace ((unsigned char) *cp
))
976 * Handles content types audio, image, and video.
977 * There's not much to do right here.
985 return OK
; /* not much to do here */
1002 CI ci
= &ct
->c_ctinfo
;
1004 /* check for missing subtype */
1005 if (!*ci
->ci_subtype
)
1006 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1009 for (kv
= SubText
; kv
->kv_key
; kv
++)
1010 if (!strcasecmp (ci
->ci_subtype
, kv
->kv_key
))
1012 ct
->c_subtype
= kv
->kv_value
;
1014 /* allocate text character set structure */
1015 if ((t
= (struct text
*) calloc (1, sizeof(*t
))) == NULL
)
1016 adios (NULL
, "out of memory");
1017 ct
->c_ctparams
= (void *) t
;
1019 /* scan for charset parameter */
1020 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1021 if (!strcasecmp (pm
->pm_name
, "charset"))
1024 /* check if content specified a character set */
1026 chset
= pm
->pm_value
;
1027 t
->tx_charset
= CHARSET_SPECIFIED
;
1029 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1033 * If we can not handle character set natively,
1034 * then check profile for string to modify the
1035 * terminal or display method.
1037 * termproc is for mhshow, though mhlist -debug prints it, too.
1039 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1040 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1041 if ((cp
= context_find (buffer
)))
1042 ct
->c_termproc
= getcpy (cp
);
1054 InitMultiPart (CT ct
)
1060 char *bp
, buffer
[BUFSIZ
];
1061 struct multipart
*m
;
1063 struct part
*part
, **next
;
1064 CI ci
= &ct
->c_ctinfo
;
1069 * The encoding for multipart messages must be either
1070 * 7bit, 8bit, or binary (per RFC2045).
1072 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1073 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1074 /* Copy the Content-Transfer-Encoding header field body so we can
1075 remove any trailing whitespace and leading blanks from it. */
1076 char *cte
= add (ct
->c_celine
? ct
->c_celine
: "(null)", NULL
);
1078 bp
= cte
+ strlen (cte
) - 1;
1079 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1080 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1083 "\"%s/%s\" type in message %s must be encoded in\n"
1084 "7bit, 8bit, or binary, per RFC 2045 (6.4). One workaround "
1085 "is to\nmanually edit the file and change the \"%s\"\n"
1086 "Content-Transfer-Encoding to one of those. For now",
1087 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1094 for (kv
= SubMultiPart
; kv
->kv_key
; kv
++)
1095 if (!strcasecmp (ci
->ci_subtype
, kv
->kv_key
))
1097 ct
->c_subtype
= kv
->kv_value
;
1100 * Check for "boundary" parameter, which is
1101 * required for multipart messages.
1104 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1105 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1111 /* complain if boundary parameter is missing */
1114 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1115 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1119 /* allocate primary structure for multipart info */
1120 if ((m
= (struct multipart
*) calloc (1, sizeof(*m
))) == NULL
)
1121 adios (NULL
, "out of memory");
1122 ct
->c_ctparams
= (void *) m
;
1124 /* check if boundary parameter contains only whitespace characters */
1125 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1128 advise (NULL
, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1129 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1133 /* remove trailing whitespace from boundary parameter */
1134 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1135 if (!isspace ((unsigned char) *dp
))
1139 /* record boundary separators */
1140 m
->mp_start
= concat (bp
, "\n", NULL
);
1141 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1143 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1144 advise (ct
->c_file
, "unable to open for reading");
1148 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1150 next
= &m
->mp_parts
;
1154 while (fgets (buffer
, sizeof(buffer
) - 1, fp
)) {
1158 pos
+= strlen (buffer
);
1159 if (buffer
[0] != '-' || buffer
[1] != '-')
1162 if (strcmp (buffer
+ 2, m
->mp_start
))
1165 if ((part
= (struct part
*) calloc (1, sizeof(*part
))) == NULL
)
1166 adios (NULL
, "out of memory");
1168 next
= &part
->mp_next
;
1170 if (!(p
= get_content (fp
, ct
->c_file
,
1171 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1178 fseek (fp
, pos
, SEEK_SET
);
1181 if (strcmp (buffer
+ 2, m
->mp_start
) == 0) {
1185 p
->c_end
= ftell(fp
) - (strlen(buffer
) + 1);
1186 if (p
->c_end
< p
->c_begin
)
1187 p
->c_begin
= p
->c_end
;
1192 if (strcmp (buffer
+ 2, m
->mp_stop
) == 0)
1198 if (! suppress_bogus_mp_content_warning
) {
1199 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1201 bogus_mp_content
= 1;
1203 if (!inout
&& part
) {
1205 p
->c_end
= ct
->c_end
;
1207 if (p
->c_begin
>= p
->c_end
) {
1208 for (next
= &m
->mp_parts
; *next
!= part
;
1209 next
= &((*next
)->mp_next
))
1213 free ((char *) part
);
1218 /* reverse the order of the parts for multipart/alternative */
1219 if (ct
->c_subtype
== MULTI_ALTERNATE
)
1223 * label all subparts with part number, and
1224 * then initialize the content of the subpart.
1229 char partnam
[BUFSIZ
];
1232 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1233 pp
= partnam
+ strlen (partnam
);
1238 for (part
= m
->mp_parts
, partnum
= 1; part
;
1239 part
= part
->mp_next
, partnum
++) {
1242 sprintf (pp
, "%d", partnum
);
1243 p
->c_partno
= add (partnam
, NULL
);
1245 /* initialize the content of the subparts */
1246 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1254 get_leftover_mp_content (ct
, 1);
1255 get_leftover_mp_content (ct
, 0);
1264 * reverse the order of the parts of a multipart/alternative
1268 reverse_parts (CT ct
)
1270 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1274 /* Reverse the order of its parts by walking the mp_parts list
1275 and pushing each node to the front. */
1276 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1277 next
= part
->mp_next
;
1278 part
->mp_next
= m
->mp_parts
;
1292 CI ci
= &ct
->c_ctinfo
;
1294 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1296 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1297 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1301 /* check for missing subtype */
1302 if (!*ci
->ci_subtype
)
1303 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1306 for (kv
= SubMessage
; kv
->kv_key
; kv
++)
1307 if (!strcasecmp (ci
->ci_subtype
, kv
->kv_key
))
1309 ct
->c_subtype
= kv
->kv_value
;
1311 switch (ct
->c_subtype
) {
1312 case MESSAGE_RFC822
:
1315 case MESSAGE_PARTIAL
:
1320 if ((p
= (struct partial
*) calloc (1, sizeof(*p
))) == NULL
)
1321 adios (NULL
, "out of memory");
1322 ct
->c_ctparams
= (void *) p
;
1324 /* scan for parameters "id", "number", and "total" */
1325 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1326 if (!strcasecmp (pm
->pm_name
, "id")) {
1327 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1330 if (!strcasecmp (pm
->pm_name
, "number")) {
1331 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1332 || p
->pm_partno
< 1) {
1335 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1336 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1337 ct
->c_file
, TYPE_FIELD
);
1342 if (!strcasecmp (pm
->pm_name
, "total")) {
1343 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1352 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1354 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1355 ci
->ci_type
, ci
->ci_subtype
,
1356 ct
->c_file
, TYPE_FIELD
);
1362 case MESSAGE_EXTERNAL
:
1369 if ((e
= (struct exbody
*) calloc (1, sizeof(*e
))) == NULL
)
1370 adios (NULL
, "out of memory");
1371 ct
->c_ctparams
= (void *) e
;
1374 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1375 advise (ct
->c_file
, "unable to open for reading");
1379 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1381 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1389 p
->c_ceopenfnx
= NULL
;
1390 if ((exresult
= params_external (ct
, 0)) != NOTOK
1391 && p
->c_ceopenfnx
== openMail
) {
1395 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1397 content_error (NULL
, ct
,
1398 "empty body for access-type=mail-server");
1402 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1403 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1405 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1407 adios ("failed", "fread");
1410 adios (NULL
, "unexpected EOF from fread");
1413 bp
+= cc
, size
-= cc
;
1420 p
->c_end
= p
->c_begin
;
1425 if (exresult
== NOTOK
)
1427 if (e
->eb_flags
== NOTOK
)
1430 switch (p
->c_type
) {
1435 if (p
->c_subtype
!= MESSAGE_RFC822
)
1439 e
->eb_partno
= ct
->c_partno
;
1441 (*p
->c_ctinitfnx
) (p
);
1456 params_external (CT ct
, int composing
)
1459 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1460 CI ci
= &ct
->c_ctinfo
;
1462 ct
->c_ceopenfnx
= NULL
;
1463 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1464 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1465 struct str2init
*s2i
;
1466 CT p
= e
->eb_content
;
1468 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1469 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1472 e
->eb_access
= pm
->pm_value
;
1473 e
->eb_flags
= NOTOK
;
1474 p
->c_encoding
= CE_EXTERNAL
;
1477 e
->eb_access
= s2i
->si_key
;
1478 e
->eb_flags
= s2i
->si_val
;
1479 p
->c_encoding
= CE_EXTERNAL
;
1481 /* Call the Init function for this external type */
1482 if ((*s2i
->si_init
)(p
) == NOTOK
)
1486 if (!strcasecmp (pm
->pm_name
, "name")) {
1487 e
->eb_name
= pm
->pm_value
;
1490 if (!strcasecmp (pm
->pm_name
, "permission")) {
1491 e
->eb_permission
= pm
->pm_value
;
1494 if (!strcasecmp (pm
->pm_name
, "site")) {
1495 e
->eb_site
= pm
->pm_value
;
1498 if (!strcasecmp (pm
->pm_name
, "directory")) {
1499 e
->eb_dir
= pm
->pm_value
;
1502 if (!strcasecmp (pm
->pm_name
, "mode")) {
1503 e
->eb_mode
= pm
->pm_value
;
1506 if (!strcasecmp (pm
->pm_name
, "size")) {
1507 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1510 if (!strcasecmp (pm
->pm_name
, "server")) {
1511 e
->eb_server
= pm
->pm_value
;
1514 if (!strcasecmp (pm
->pm_name
, "subject")) {
1515 e
->eb_subject
= pm
->pm_value
;
1518 if (!strcasecmp (pm
->pm_name
, "url")) {
1520 * According to RFC 2017, we have to remove all whitespace from
1524 char *u
, *p
= pm
->pm_value
;
1525 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1527 for (; *p
!= '\0'; p
++) {
1528 if (! isspace((unsigned char) *p
))
1535 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1536 e
->eb_body
= getcpy (pm
->pm_value
);
1541 if (!e
->eb_access
) {
1543 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1544 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1557 InitApplication (CT ct
)
1560 CI ci
= &ct
->c_ctinfo
;
1563 for (kv
= SubApplication
; kv
->kv_key
; kv
++)
1564 if (!strcasecmp (ci
->ci_subtype
, kv
->kv_key
))
1566 ct
->c_subtype
= kv
->kv_value
;
1573 * TRANSFER ENCODINGS
1577 init_encoding (CT ct
, OpenCEFunc openfnx
)
1579 ct
->c_ceopenfnx
= openfnx
;
1580 ct
->c_ceclosefnx
= close_encoding
;
1581 ct
->c_cesizefnx
= size_encoding
;
1588 close_encoding (CT ct
)
1590 CE ce
= &ct
->c_cefile
;
1599 static unsigned long
1600 size_encoding (CT ct
)
1605 CE ce
= &ct
->c_cefile
;
1608 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1609 return (long) st
.st_size
;
1612 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1613 return (long) st
.st_size
;
1618 if (ct
->c_encoding
== CE_EXTERNAL
)
1619 return (ct
->c_end
- ct
->c_begin
);
1622 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1623 return (ct
->c_end
- ct
->c_begin
);
1625 if (fstat (fd
, &st
) != NOTOK
)
1626 size
= (long) st
.st_size
;
1630 (*ct
->c_ceclosefnx
) (ct
);
1639 static unsigned char b642nib
[0x80] = {
1640 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1641 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1642 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1643 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1644 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1645 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1646 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1647 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1648 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
1649 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1650 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1651 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1652 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1653 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1654 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1655 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1662 return init_encoding (ct
, openBase64
);
1667 openBase64 (CT ct
, char **file
)
1669 int bitno
, cc
, digested
;
1670 int fd
, len
, skip
, own_ct_fp
= 0, text
= ct
->c_type
== CT_TEXT
;
1672 unsigned char value
, b
;
1673 char *cp
, *ep
, buffer
[BUFSIZ
];
1674 /* sbeck -- handle suffixes */
1676 CE ce
= &ct
->c_cefile
;
1680 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1685 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1686 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1692 if (*file
== NULL
) {
1695 ce
->ce_file
= add (*file
, NULL
);
1699 /* sbeck@cise.ufl.edu -- handle suffixes */
1701 snprintf (buffer
, sizeof(buffer
), "%s-suffix-%s/%s",
1702 invo_name
, ci
->ci_type
, ci
->ci_subtype
);
1703 cp
= context_find (buffer
);
1704 if (cp
== NULL
|| *cp
== '\0') {
1705 snprintf (buffer
, sizeof(buffer
), "%s-suffix-%s", invo_name
,
1707 cp
= context_find (buffer
);
1709 if (cp
!= NULL
&& *cp
!= '\0') {
1710 if (ce
->ce_unlink
) {
1711 /* Create temporary file with filename extension. */
1712 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1713 adios(NULL
, "unable to create temporary file in %s",
1717 ce
->ce_file
= add (cp
, ce
->ce_file
);
1719 } else if (*file
== NULL
) {
1721 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1722 adios(NULL
, "unable to create temporary file in %s",
1725 ce
->ce_file
= add (tempfile
, NULL
);
1728 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1729 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1733 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1734 adios (NULL
, "internal error(1)");
1737 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1738 content_error (ct
->c_file
, ct
, "unable to open for reading");
1744 if ((digested
= ct
->c_digested
))
1745 MD5Init (&mdContext
);
1751 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1753 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
1755 content_error (ct
->c_file
, ct
, "error reading from");
1759 content_error (NULL
, ct
, "premature eof");
1767 for (ep
= (cp
= buffer
) + cc
; cp
< ep
; cp
++) {
1770 if (isspace ((unsigned char) *cp
))
1772 if (skip
|| (((unsigned char) *cp
) & 0x80)
1773 || (value
= b642nib
[((unsigned char) *cp
) & 0x7f]) > 0x3f) {
1775 fprintf (stderr
, "*cp=0x%x pos=%ld skip=%d\n",
1776 (unsigned char) *cp
,
1777 (long) (lseek (fd
, (off_t
) 0, SEEK_CUR
) - (ep
- cp
)),
1780 content_error (NULL
, ct
,
1781 "invalid BASE64 encoding -- continuing");
1785 bits
|= value
<< bitno
;
1787 if ((bitno
-= 6) < 0) {
1788 b
= (bits
>> 16) & 0xff;
1789 if (!text
|| b
!= '\r')
1790 putc ((char) b
, ce
->ce_fp
);
1792 MD5Update (&mdContext
, &b
, 1);
1794 b
= (bits
>> 8) & 0xff;
1795 if (! text
|| b
!= '\r')
1796 putc ((char) b
, ce
->ce_fp
);
1798 MD5Update (&mdContext
, &b
, 1);
1801 if (! text
|| b
!= '\r')
1802 putc ((char) b
, ce
->ce_fp
);
1804 MD5Update (&mdContext
, &b
, 1);
1808 if (ferror (ce
->ce_fp
)) {
1809 content_error (ce
->ce_file
, ct
,
1810 "error writing to");
1813 bitno
= 18, bits
= 0L, skip
= 0;
1819 goto self_delimiting
;
1828 fprintf (stderr
, "premature ending (bitno %d)\n", bitno
);
1830 content_error (NULL
, ct
, "invalid BASE64 encoding");
1835 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1837 if (fflush (ce
->ce_fp
)) {
1838 content_error (ce
->ce_file
, ct
, "error writing to");
1843 unsigned char digest
[16];
1845 MD5Final (digest
, &mdContext
);
1846 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
1847 sizeof(digest
) / sizeof(digest
[0])))
1848 content_error (NULL
, ct
,
1849 "content integrity suspect (digest mismatch) -- continuing");
1852 fprintf (stderr
, "content integrity confirmed\n");
1855 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1858 *file
= ce
->ce_file
;
1863 return fileno (ce
->ce_fp
);
1870 free_encoding (ct
, 0);
1879 static char hex2nib
[0x80] = {
1880 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1881 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1882 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1883 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1884 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1885 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1886 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1887 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1888 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1889 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1890 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1891 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1892 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1893 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1894 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1895 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1902 return init_encoding (ct
, openQuoted
);
1907 openQuoted (CT ct
, char **file
)
1909 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1911 char buffer
[BUFSIZ
];
1913 CE ce
= &ct
->c_cefile
;
1914 /* sbeck -- handle suffixes */
1919 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1924 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1925 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1931 if (*file
== NULL
) {
1934 ce
->ce_file
= add (*file
, NULL
);
1938 /* sbeck@cise.ufl.edu -- handle suffixes */
1940 snprintf (buffer
, sizeof(buffer
), "%s-suffix-%s/%s",
1941 invo_name
, ci
->ci_type
, ci
->ci_subtype
);
1942 cp
= context_find (buffer
);
1943 if (cp
== NULL
|| *cp
== '\0') {
1944 snprintf (buffer
, sizeof(buffer
), "%s-suffix-%s", invo_name
,
1946 cp
= context_find (buffer
);
1948 if (cp
!= NULL
&& *cp
!= '\0') {
1949 if (ce
->ce_unlink
) {
1950 /* Create temporary file with filename extension. */
1951 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1952 adios(NULL
, "unable to create temporary file in %s",
1956 ce
->ce_file
= add (cp
, ce
->ce_file
);
1958 } else if (*file
== NULL
) {
1960 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1961 adios(NULL
, "unable to create temporary file in %s",
1964 ce
->ce_file
= add (tempfile
, NULL
);
1967 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1968 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1972 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1973 adios (NULL
, "internal error(2)");
1976 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1977 content_error (ct
->c_file
, ct
, "unable to open for reading");
1983 if ((digested
= ct
->c_digested
))
1984 MD5Init (&mdContext
);
1991 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1993 if (fgets (buffer
, sizeof(buffer
) - 1, ct
->c_fp
) == NULL
) {
1994 content_error (NULL
, ct
, "premature eof");
1998 if ((cc
= strlen (buffer
)) > len
)
2002 for (ep
= (cp
= buffer
) + cc
- 1; cp
<= ep
; ep
--)
2003 if (!isspace ((unsigned char) *ep
))
2007 for (; cp
< ep
; cp
++) {
2009 /* in an escape sequence */
2011 /* at byte 1 of an escape sequence */
2012 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2013 /* next is byte 2 */
2016 /* at byte 2 of an escape sequence */
2018 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2019 putc (mask
, ce
->ce_fp
);
2021 MD5Update (&mdContext
, &mask
, 1);
2022 if (ferror (ce
->ce_fp
)) {
2023 content_error (ce
->ce_file
, ct
, "error writing to");
2026 /* finished escape sequence; next may be literal or a new
2027 * escape sequence */
2030 /* on to next byte */
2034 /* not in an escape sequence */
2036 /* starting an escape sequence, or invalid '='? */
2037 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2038 /* "=\n" soft line break, eat the \n */
2042 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2043 /* We don't have 2 bytes left, so this is an invalid
2044 * escape sequence; just show the raw bytes (below). */
2045 } else if (isxdigit ((unsigned char) cp
[1]) &&
2046 isxdigit ((unsigned char) cp
[2])) {
2047 /* Next 2 bytes are hex digits, making this a valid escape
2048 * sequence; let's decode it (above). */
2052 /* One or both of the next 2 is out of range, making this
2053 * an invalid escape sequence; just show the raw bytes
2058 /* Just show the raw byte. */
2059 putc (*cp
, ce
->ce_fp
);
2062 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2064 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2067 if (ferror (ce
->ce_fp
)) {
2068 content_error (ce
->ce_file
, ct
, "error writing to");
2074 content_error (NULL
, ct
,
2075 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2079 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2081 if (fflush (ce
->ce_fp
)) {
2082 content_error (ce
->ce_file
, ct
, "error writing to");
2087 unsigned char digest
[16];
2089 MD5Final (digest
, &mdContext
);
2090 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2091 sizeof(digest
) / sizeof(digest
[0])))
2092 content_error (NULL
, ct
,
2093 "content integrity suspect (digest mismatch) -- continuing");
2096 fprintf (stderr
, "content integrity confirmed\n");
2099 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2102 *file
= ce
->ce_file
;
2107 return fileno (ce
->ce_fp
);
2110 free_encoding (ct
, 0);
2126 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2129 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2135 open7Bit (CT ct
, char **file
)
2137 int cc
, fd
, len
, own_ct_fp
= 0;
2138 char buffer
[BUFSIZ
];
2139 /* sbeck -- handle suffixes */
2142 CE ce
= &ct
->c_cefile
;
2145 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2150 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2151 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2157 if (*file
== NULL
) {
2160 ce
->ce_file
= add (*file
, NULL
);
2164 /* sbeck@cise.ufl.edu -- handle suffixes */
2166 snprintf (buffer
, sizeof(buffer
), "%s-suffix-%s/%s",
2167 invo_name
, ci
->ci_type
, ci
->ci_subtype
);
2168 cp
= context_find (buffer
);
2169 if (cp
== NULL
|| *cp
== '\0') {
2170 snprintf (buffer
, sizeof(buffer
), "%s-suffix-%s", invo_name
,
2172 cp
= context_find (buffer
);
2174 if (cp
!= NULL
&& *cp
!= '\0') {
2175 if (ce
->ce_unlink
) {
2176 /* Create temporary file with filename extension. */
2177 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2178 adios(NULL
, "unable to create temporary file in %s",
2182 ce
->ce_file
= add (cp
, ce
->ce_file
);
2184 } else if (*file
== NULL
) {
2186 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2187 adios(NULL
, "unable to create temporary file in %s",
2190 ce
->ce_file
= add (tempfile
, NULL
);
2193 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2194 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2198 if (ct
->c_type
== CT_MULTIPART
) {
2199 CI ci
= &ct
->c_ctinfo
;
2203 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2204 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2205 + 1 + strlen (ci
->ci_subtype
);
2206 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2209 fputs (buffer
, ce
->ce_fp
);
2213 if (ci
->ci_comment
) {
2214 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2215 fputs ("\n\t", ce
->ce_fp
);
2219 putc (' ', ce
->ce_fp
);
2222 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2225 fprintf (ce
->ce_fp
, "\n");
2227 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2229 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2231 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2232 fprintf (ce
->ce_fp
, "\n");
2235 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2236 adios (NULL
, "internal error(3)");
2239 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2240 content_error (ct
->c_file
, ct
, "unable to open for reading");
2246 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2248 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2250 content_error (ct
->c_file
, ct
, "error reading from");
2254 content_error (NULL
, ct
, "premature eof");
2262 fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
);
2263 if (ferror (ce
->ce_fp
)) {
2264 content_error (ce
->ce_file
, ct
, "error writing to");
2269 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2271 if (fflush (ce
->ce_fp
)) {
2272 content_error (ce
->ce_file
, ct
, "error writing to");
2276 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2279 *file
= ce
->ce_file
;
2284 return fileno (ce
->ce_fp
);
2287 free_encoding (ct
, 0);
2301 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2303 char cachefile
[BUFSIZ
];
2306 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2311 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2312 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2318 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2319 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2320 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2321 ce
->ce_file
= getcpy (cachefile
);
2325 admonish (cachefile
, "unable to fopen for reading");
2332 *file
= ce
->ce_file
;
2333 *fd
= fileno (ce
->ce_fp
);
2344 return init_encoding (ct
, openFile
);
2349 openFile (CT ct
, char **file
)
2352 char cachefile
[BUFSIZ
];
2353 struct exbody
*e
= ct
->c_ctexbody
;
2354 CE ce
= &ct
->c_cefile
;
2356 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2368 content_error (NULL
, ct
, "missing name parameter");
2372 ce
->ce_file
= getcpy (e
->eb_name
);
2375 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2376 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2380 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2381 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2382 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2386 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2387 if ((fp
= fopen (cachefile
, "w"))) {
2389 char buffer
[BUFSIZ
];
2390 FILE *gp
= ce
->ce_fp
;
2392 fseek (gp
, 0L, SEEK_SET
);
2394 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2396 fwrite (buffer
, sizeof(*buffer
), cc
, fp
);
2400 admonish (ce
->ce_file
, "error reading");
2401 (void) m_unlink (cachefile
);
2405 admonish (cachefile
, "error writing");
2406 (void) m_unlink (cachefile
);
2413 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2414 *file
= ce
->ce_file
;
2415 return fileno (ce
->ce_fp
);
2425 return init_encoding (ct
, openFTP
);
2430 openFTP (CT ct
, char **file
)
2432 int cachetype
, caching
, fd
;
2434 char *bp
, *ftp
, *user
, *pass
;
2435 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2437 CE ce
= &ct
->c_cefile
;
2438 static char *username
= NULL
;
2439 static char *password
= NULL
;
2443 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2449 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2460 if (!e
->eb_name
|| !e
->eb_site
) {
2461 content_error (NULL
, ct
, "missing %s parameter",
2462 e
->eb_name
? "site": "name");
2466 /* Get the buffer ready to go */
2468 buflen
= sizeof(buffer
);
2471 * Construct the query message for user
2473 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2479 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2485 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2486 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2491 if (e
->eb_size
> 0) {
2492 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2497 snprintf (bp
, buflen
, "? ");
2500 * Now, check the answer
2502 if (!getanswer (buffer
))
2507 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2511 ruserpass (e
->eb_site
, &username
, &password
);
2516 ce
->ce_unlink
= (*file
== NULL
);
2518 cachefile
[0] = '\0';
2519 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2520 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2521 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2522 if (*file
== NULL
) {
2529 ce
->ce_file
= add (*file
, NULL
);
2531 ce
->ce_file
= add (cachefile
, NULL
);
2534 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2535 adios(NULL
, "unable to create temporary file in %s",
2538 ce
->ce_file
= add (tempfile
, NULL
);
2541 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2542 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2547 int child_id
, i
, vecp
;
2551 vec
[vecp
++] = r1bindex (ftp
, '/');
2552 vec
[vecp
++] = e
->eb_site
;
2555 vec
[vecp
++] = e
->eb_dir
;
2556 vec
[vecp
++] = e
->eb_name
;
2557 vec
[vecp
++] = ce
->ce_file
,
2558 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2559 ? "ascii" : "binary";
2564 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2568 adios ("fork", "unable to");
2572 close (fileno (ce
->ce_fp
));
2574 fprintf (stderr
, "unable to exec ");
2580 if (pidXwait (child_id
, NULL
)) {
2581 username
= password
= NULL
;
2591 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2596 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2597 if ((fp
= fopen (cachefile
, "w"))) {
2599 FILE *gp
= ce
->ce_fp
;
2601 fseek (gp
, 0L, SEEK_SET
);
2603 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2605 fwrite (buffer
, sizeof(*buffer
), cc
, fp
);
2609 admonish (ce
->ce_file
, "error reading");
2610 (void) m_unlink (cachefile
);
2614 admonish (cachefile
, "error writing");
2615 (void) m_unlink (cachefile
);
2623 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2624 *file
= ce
->ce_file
;
2625 return fileno (ce
->ce_fp
);
2636 return init_encoding (ct
, openMail
);
2641 openMail (CT ct
, char **file
)
2643 int child_id
, fd
, i
, vecp
;
2645 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2646 struct exbody
*e
= ct
->c_ctexbody
;
2647 CE ce
= &ct
->c_cefile
;
2649 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2660 if (!e
->eb_server
) {
2661 content_error (NULL
, ct
, "missing server parameter");
2665 /* Get buffer ready to go */
2667 buflen
= sizeof(buffer
);
2669 /* Now, construct query message */
2670 snprintf (bp
, buflen
, "Retrieve content");
2676 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2682 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2684 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2686 /* Now, check answer */
2687 if (!getanswer (buffer
))
2691 vec
[vecp
++] = r1bindex (mailproc
, '/');
2692 vec
[vecp
++] = e
->eb_server
;
2693 vec
[vecp
++] = "-subject";
2694 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2695 vec
[vecp
++] = "-body";
2696 vec
[vecp
++] = e
->eb_body
;
2699 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2703 advise ("fork", "unable to");
2707 execvp (mailproc
, vec
);
2708 fprintf (stderr
, "unable to exec ");
2714 if (pidXwait (child_id
, NULL
) == OK
)
2715 advise (NULL
, "request sent");
2719 if (*file
== NULL
) {
2721 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2722 adios(NULL
, "unable to create temporary file in %s",
2725 ce
->ce_file
= add (tempfile
, NULL
);
2728 ce
->ce_file
= add (*file
, NULL
);
2732 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2733 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2737 /* showproc is for mhshow and mhstore, though mhlist -debug
2738 * prints it, too. */
2740 free (ct
->c_showproc
);
2741 ct
->c_showproc
= add ("true", NULL
);
2743 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2744 *file
= ce
->ce_file
;
2745 return fileno (ce
->ce_fp
);
2756 return init_encoding (ct
, openURL
);
2761 openURL (CT ct
, char **file
)
2763 struct exbody
*e
= ct
->c_ctexbody
;
2764 CE ce
= &ct
->c_cefile
;
2765 char *urlprog
, *program
;
2766 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2767 int fd
, caching
, cachetype
;
2768 struct msgs_array args
= { 0, 0, NULL
};
2771 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2775 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2779 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2791 content_error(NULL
, ct
, "missing url parameter");
2795 ce
->ce_unlink
= (*file
== NULL
);
2797 cachefile
[0] = '\0';
2799 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2800 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2801 if (*file
== NULL
) {
2808 ce
->ce_file
= add(*file
, NULL
);
2810 ce
->ce_file
= add(cachefile
, NULL
);
2813 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2814 adios(NULL
, "unable to create temporary file in %s",
2817 ce
->ce_file
= add (tempfile
, NULL
);
2820 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2821 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2825 switch (child_id
= fork()) {
2827 adios ("fork", "unable to");
2831 argsplit_msgarg(&args
, urlprog
, &program
);
2832 app_msgarg(&args
, e
->eb_url
);
2833 app_msgarg(&args
, NULL
);
2834 dup2(fileno(ce
->ce_fp
), 1);
2835 close(fileno(ce
->ce_fp
));
2836 execvp(program
, args
.msgs
);
2837 fprintf(stderr
, "Unable to exec ");
2843 if (pidXwait(child_id
, NULL
)) {
2851 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2856 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2857 if ((fp
= fopen(cachefile
, "w"))) {
2859 FILE *gp
= ce
->ce_fp
;
2861 fseeko(gp
, 0, SEEK_SET
);
2863 while ((cc
= fread(buffer
, sizeof(*buffer
),
2864 sizeof(buffer
), gp
)) > 0)
2865 fwrite(buffer
, sizeof(*buffer
), cc
, fp
);
2870 admonish(ce
->ce_file
, "error reading");
2871 (void) m_unlink (cachefile
);
2878 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2879 *file
= ce
->ce_file
;
2884 readDigest (CT ct
, char *cp
)
2889 unsigned char *dp
, value
, *ep
;
2895 for (ep
= (dp
= ct
->c_digest
)
2896 + sizeof(ct
->c_digest
) / sizeof(ct
->c_digest
[0]); *cp
; cp
++)
2901 || (value
= b642nib
[*cp
& 0x7f]) > 0x3f) {
2903 fprintf (stderr
, "invalid BASE64 encoding\n");
2907 bits
|= value
<< bitno
;
2909 if ((bitno
-= 6) < 0) {
2910 if (dp
+ (3 - skip
) > ep
)
2911 goto invalid_digest
;
2912 *dp
++ = (bits
>> 16) & 0xff;
2914 *dp
++ = (bits
>> 8) & 0xff;
2916 *dp
++ = bits
& 0xff;
2926 goto self_delimiting
;
2931 fprintf (stderr
, "premature ending (bitno %d)\n", bitno
);
2941 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2949 fprintf (stderr
, "MD5 digest=");
2950 for (dp
= ct
->c_digest
; dp
< ep
; dp
++)
2951 fprintf (stderr
, "%02x", *dp
& 0xff);
2952 fprintf (stderr
, "\n");
2959 /* Multipart parts might have content before the first subpart and/or
2960 after the last subpart that hasn't been stored anywhere else, so do
2963 get_leftover_mp_content (CT ct
, int before
/* or after */) {
2964 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2966 int found_boundary
= 0;
2967 char buffer
[BUFSIZ
];
2970 char *content
= NULL
;
2972 if (! m
) return NOTOK
;
2975 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2977 /* Isolate the beginning of this part to the beginning of the
2978 first subpart and save any content between them. */
2979 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2980 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2981 boundary
= concat ("--", m
->mp_start
, NULL
);
2983 struct part
*last_subpart
= NULL
;
2984 struct part
*subpart
;
2986 /* Go to the last subpart to get its end position. */
2987 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2988 last_subpart
= subpart
;
2991 if (last_subpart
== NULL
) return NOTOK
;
2993 /* Isolate the end of the last subpart to the end of this part
2994 and save any content between them. */
2995 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2996 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2997 boundary
= concat ("--", m
->mp_stop
, NULL
);
3000 /* Back up by 1 to pick up the newline. */
3001 while (fgets (buffer
, sizeof(buffer
) - 1, ct
->c_fp
)) {
3002 read
+= strlen (buffer
);
3003 /* Don't look beyond beginning of first subpart (before) or
3004 next part (after). */
3005 if (read
> max
) buffer
[read
-max
] = '\0';
3008 if (! strcmp (buffer
, boundary
)) {
3012 if (! found_boundary
&& ! strcmp (buffer
, boundary
)) {
3018 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3020 char *old_content
= content
;
3021 content
= concat (content
, buffer
, NULL
);
3025 ? concat ("\n", buffer
, NULL
)
3026 : concat (buffer
, NULL
);
3031 if (found_boundary
|| read
> max
) break;
3033 if (read
> max
) break;
3037 /* Skip the newline if that's all there is. */
3041 /* Remove trailing newline, except at EOF. */
3042 if ((before
|| ! feof (ct
->c_fp
)) &&
3043 (cp
= content
+ strlen (content
)) > content
&&
3048 if (strlen (content
) > 1) {
3050 m
->mp_content_before
= content
;
3052 m
->mp_content_after
= content
;
3066 ct_type_str (int type
) {
3068 case CT_APPLICATION
:
3069 return "application";
3085 return "unknown_type";
3091 ct_subtype_str (int type
, int subtype
) {
3093 case CT_APPLICATION
:
3095 case APPLICATION_OCTETS
:
3097 case APPLICATION_POSTSCRIPT
:
3098 return "postscript";
3100 return "unknown_app_subtype";
3104 case MESSAGE_RFC822
:
3106 case MESSAGE_PARTIAL
:
3108 case MESSAGE_EXTERNAL
:
3111 return "unknown_msg_subtype";
3117 case MULTI_ALTERNATE
:
3118 return "alternative";
3121 case MULTI_PARALLEL
:
3124 return "unknown_multipart_subtype";
3135 return "unknown_text_subtype";
3138 return "unknown_type";
3143 /* Find the content type and InitFunc for the CT. */
3144 const struct str2init
*
3145 get_ct_init (int type
) {
3146 const struct str2init
*sp
;
3148 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3149 if (type
== sp
->si_val
) {
3158 ce_str (int encoding
) {
3163 return "quoted-printable";
3179 /* Find the content type and InitFunc for the content encoding method. */
3180 const struct str2init
*
3181 get_ce_method (const char *method
) {
3182 struct str2init
*sp
;
3184 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3185 if (! strcasecmp (method
, sp
->si_key
)) {
3194 * Parse a series of MIME attributes (or parameters) given a header as
3197 * Arguments include:
3199 * filename - Name of input file (for error messages)
3200 * fieldname - Name of field being processed
3201 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3202 * Updated to point to end of attributes when finished.
3203 * param_head - Pointer to head of parameter list
3204 * param_tail - Pointer to tail of parameter list
3205 * commentp - Pointer to header comment pointer (may be NULL)
3207 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3208 * DONE to indicate a benign error (minor parsing error, but the program
3213 parse_header_attrs (const char *filename
, const char *fieldname
,
3214 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3217 char *cp
= *header_attrp
;
3223 struct sectlist
*next
;
3229 struct sectlist
*sechead
;
3230 struct parmlist
*next
;
3231 } *pp
, *pp2
, *phead
= NULL
;
3233 while (*cp
== ';') {
3234 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3235 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3238 while (isspace ((unsigned char) *cp
))
3242 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3248 "extraneous trailing ';' in message %s's %s: "
3250 filename
, fieldname
);
3254 /* down case the attribute name */
3255 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3256 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
3257 *dp
= tolower ((unsigned char) *dp
);
3259 for (up
= dp
; isspace ((unsigned char) *dp
);)
3261 if (dp
== cp
|| *dp
!= '=') {
3263 "invalid parameter in message %s's %s: "
3264 "field\n%*sparameter %s (error detected at offset %d)",
3265 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3270 * To handle RFC 2231, we have to deal with the following extensions:
3272 * name*=encoded-value
3273 * name*<N>=part-N-of-a-parameter-value
3274 * name*<N>*=encoded-part-N-of-a-parameter-value
3277 * If there's a * right before the equal sign, it's encoded.
3278 * If there's a * and one or more digits, then it's section N.
3280 * Remember we can have one or the other, or both. cp points to
3281 * beginning of name, up points past the last character in the
3285 for (vp
= cp
; vp
< up
; vp
++) {
3286 if (*vp
== '*' && vp
< up
- 1) {
3289 } else if (*vp
== '*' && vp
== up
- 1) {
3291 } else if (partial
) {
3292 if (isdigit((unsigned char) *vp
))
3293 index
= *vp
- '0' + index
* 10;
3295 advise (NULL
, "invalid parameter index in message %s's "
3296 "%s: field\n%*s(parameter %s)", filename
,
3297 fieldname
, strlen(invo_name
) + 2, "", cp
);
3306 * Break out the parameter name and value sections and allocate
3310 nameptr
= mh_xmalloc(len
+ 1);
3311 strncpy(nameptr
, cp
, len
);
3312 nameptr
[len
] = '\0';
3314 for (dp
++; isspace ((unsigned char) *dp
);)
3319 * Single quotes delimit the character set and language tag.
3320 * They are required on the first section (or a complete
3325 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3331 charset
= mh_xmalloc(len
+ 1);
3332 strncpy(charset
, dp
, len
);
3333 charset
[len
] = '\0';
3339 advise(NULL
, "missing charset in message %s's %s: "
3340 "field\n%*s(parameter %s)", filename
, fieldname
,
3341 strlen(invo_name
) + 2, "", nameptr
);
3347 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3354 lang
= mh_xmalloc(len
+ 1);
3355 strncpy(lang
, dp
, len
);
3362 advise(NULL
, "missing language tag in message %s's %s: "
3363 "field\n%*s(parameter %s)", filename
, fieldname
,
3364 strlen(invo_name
) + 2, "", nameptr
);
3375 * At this point vp should be pointing at the beginning
3376 * of the encoded value/section. Continue until we reach
3377 * the end or get whitespace. But first, calculate the
3378 * length so we can allocate the correct buffer size.
3381 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3383 if (*(vp
+ 1) == '\0' ||
3384 !isxdigit((unsigned char) *(vp
+ 1)) ||
3385 *(vp
+ 2) == '\0' ||
3386 !isxdigit((unsigned char) *(vp
+ 2))) {
3387 advise(NULL
, "invalid encoded sequence in message "
3388 "%s's %s: field\n%*s(parameter %s)",
3389 filename
, fieldname
, strlen(invo_name
) + 2,
3403 up
= valptr
= mh_xmalloc(len
+ 1);
3405 for (vp
= dp
; istoken(*vp
); vp
++) {
3407 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3418 * A "normal" string. If it's got a leading quote, then we
3419 * strip the quotes out. Otherwise go until we reach the end
3420 * or get whitespace. Note we scan it twice; once to get the
3421 * length, then the second time copies it into the destination
3428 for (cp
= dp
+ 1;;) {
3433 "invalid quoted-string in message %s's %s: "
3434 "field\n%*s(parameter %s)",
3435 filename
, fieldname
, strlen(invo_name
) + 2, "",
3458 for (cp
= dp
; istoken (*cp
); cp
++) {
3463 valptr
= mh_xmalloc(len
+ 1);
3467 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3475 strncpy(valptr
, cp
= dp
, len
);
3483 * If 'partial' is set, we don't allocate a parameter now. We
3484 * put it on the parameter linked list to be reassembled later.
3486 * "phead" points to a list of all parameters we need to reassemble.
3487 * Each parameter has a list of sections. We insert the sections in
3492 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3493 if (strcasecmp(nameptr
, pp
->name
) == 0)
3498 pp
= mh_xmalloc(sizeof(*pp
));
3499 memset(pp
, 0, sizeof(*pp
));
3506 * Insert this into the section linked list
3509 sp
= mh_xmalloc(sizeof(*sp
));
3510 memset(sp
, 0, sizeof(*sp
));
3515 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3516 sp
->next
= pp
->sechead
;
3519 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3520 if (sp2
->index
== sp
->index
) {
3521 advise (NULL
, "duplicate index (%d) in message "
3522 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3523 filename
, fieldname
, strlen(invo_name
) + 2, "",
3527 if (sp2
->index
< sp
->index
&&
3528 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3529 sp
->next
= sp2
->next
;
3536 advise(NULL
, "Internal error: cannot insert partial "
3537 "param in message %s's %s: field\n%*s(parameter %s)",
3538 filename
, fieldname
, strlen(invo_name
) + 2, "",
3545 * Save our charset and lang tags.
3548 if (index
== 0 && encoded
) {
3551 pp
->charset
= charset
;
3557 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3558 pm
->pm_charset
= charset
;
3562 while (isspace ((unsigned char) *cp
))
3566 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3572 * Now that we're done, reassemble all of the partial parameters.
3575 for (pp
= phead
; pp
!= NULL
; ) {
3579 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3580 if (sp
->index
!= pindex
++) {
3581 advise(NULL
, "missing section %d for parameter in "
3582 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3583 filename
, fieldname
, strlen(invo_name
) + 2, "",
3590 p
= q
= mh_xmalloc(tlen
+ 1);
3591 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3592 memcpy(q
, sp
->value
, sp
->len
);
3602 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3603 pm
->pm_charset
= pp
->charset
;
3604 pm
->pm_lang
= pp
->lang
;
3615 * Return the charset for a particular content type. Return pointer is
3616 * only valid until the next call to content_charset().
3620 content_charset (CT ct
) {
3621 static char *ret_charset
= NULL
;
3623 if (ret_charset
!= NULL
) {
3627 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3629 return ret_charset
? ret_charset
: "US-ASCII";
3634 * Create a string based on a list of output parameters. Assume that this
3635 * parameter string will be appended to an existing header, so start out
3636 * with the separator (;). Perform RFC 2231 encoding when necessary.
3640 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3642 char *paramout
= NULL
;
3643 char line
[CPERLIN
* 2], *q
;
3644 int curlen
, index
, cont
, encode
, i
;
3645 size_t valoff
, numchars
;
3647 while (params
!= NULL
) {
3653 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3656 if (strlen(params
->pm_name
) > CPERLIN
) {
3657 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3663 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3666 * Loop until we get a parameter that fits within a line. We
3667 * assume new lines start with a tab, so check our overflow based
3677 * At this point we're definitely continuing the line, so
3678 * be sure to include the parameter name and section index.
3681 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3682 params
->pm_name
, index
);
3685 * Both of these functions do a NUL termination
3689 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3690 numchars
, valoff
, index
);
3692 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3703 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3708 * "line" starts with a ;\n\t, so that doesn't count against
3709 * the length. But add 8 since it starts with a tab; that's
3710 * how we end up with 5.
3713 initialwidth
= strlen(line
) + 5;
3716 * At this point the line should be built, so add it to our
3717 * current output buffer.
3720 paramout
= add(line
, paramout
);
3724 * If this won't fit on the line, start a new one. Save room in
3725 * case we need a semicolon on the end
3728 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3740 * At this point, we're either finishing a contined parameter, or
3741 * we're working on a new one.
3745 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3746 params
->pm_name
, index
);
3748 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3753 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3754 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3756 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3757 strlen(params
->pm_value
+ valoff
), valoff
);
3765 paramout
= add(line
, paramout
);
3766 initialwidth
+= strlen(line
);
3768 params
= params
->pm_next
;
3772 *offsetout
= initialwidth
;
3778 * Calculate the size of a parameter.
3782 * pm - The parameter being output
3783 * index - If continuing the parameter, the index of the section
3785 * valueoff - The current offset into the parameter value that we're
3786 * working on (previous sections have consumed valueoff bytes).
3787 * encode - Set if we should perform encoding on this parameter section
3788 * (given that we're consuming bytesfit bytes).
3789 * cont - Set if the remaining data in value will not fit on a single
3790 * line and will need to be continued.
3791 * bytesfit - The number of bytes that we can consume from the parameter
3792 * value and still fit on a completely new line. The
3793 * calculation assumes the new line starts with a tab,
3794 * includes the parameter name and any encoding, and fits
3795 * within CPERLIN bytes. Will always be at least 1.
3799 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3802 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3803 size_t len
= 0, fit
= 0;
3804 int fitlimit
= 0, eightbit
, maxfit
;
3809 * Add up the length. First, start with the parameter name.
3812 len
= strlen(pm
->pm_name
);
3815 * Scan the parameter value and see if we need to do encoding for this
3819 eightbit
= contains8bit(start
, NULL
);
3822 * Determine if we need to encode this section. Encoding is necessary if:
3824 * - There are any 8-bit characters at all and we're on the first
3826 * - There are 8-bit characters within N bytes of our section start.
3827 * N is calculated based on the number of bytes it would take to
3828 * reach CPERLIN. Specifically:
3829 * 8 (starting tab) +
3830 * strlen(param name) +
3831 * 4 ('* for section marker, '=', opening/closing '"')
3833 * is the number of bytes used by everything that isn't part of the
3834 * value. So that gets subtracted from CPERLIN.
3837 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3838 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3839 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3843 len
++; /* Add in equal sign */
3847 * We're using maxfit as a marker for how many characters we can
3848 * fit into the line. Bump it by two because we're not using quotes
3855 * If we don't have a charset or language tag in this parameter,
3859 if (! pm
->pm_charset
)
3860 pm
->pm_charset
= getcpy(write_charset_8bit());
3862 pm
->pm_lang
= getcpy(NULL
); /* Default to a blank lang tag */
3864 len
++; /* For the encoding marker */
3867 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3872 * We know we definitely need to include an index. maxfit already
3873 * includes the section marker.
3875 len
+= strlen(indexchar
);
3877 for (p
= start
; *p
!= '\0'; p
++) {
3878 if (isparamencode(*p
)) {
3886 * Just so there's no confusion: maxfit is counting OUTPUT
3887 * characters (post-encoding). fit is counting INPUT characters.
3889 if (! fitlimit
&& maxfit
>= 0)
3891 else if (! fitlimit
)
3896 * Calculate the string length, but add room for quoting \
3897 * and " if necessary. Also account for quotes at beginning
3900 for (p
= start
; *p
!= '\0'; p
++) {
3911 if (! fitlimit
&& maxfit
>= 0)
3913 else if (! fitlimit
)
3930 * Output an encoded parameter string.
3934 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3935 size_t valueoff
, int index
)
3937 size_t outlen
= 0, n
;
3938 char *endptr
= output
+ len
, *p
;
3941 * First, output the marker for an encoded string.
3949 * If the index is 0, output the character set and language tag.
3950 * If theses were NULL, they should have already been filled in
3955 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3959 if (output
> endptr
) {
3960 advise(NULL
, "Internal error: parameter buffer overflow");
3966 * Copy over the value, encoding if necessary
3969 p
= pm
->pm_value
+ valueoff
;
3970 while (valuelen
-- > 0) {
3971 if (isparamencode(*p
)) {
3972 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
3979 if (output
> endptr
) {
3980 advise(NULL
, "Internal error: parameter buffer overflow");
3991 * Output a "normal" parameter, without encoding. Be sure to escape
3992 * quotes and backslashes if necessary.
3996 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4000 char *endptr
= output
+ len
, *p
;
4006 p
= pm
->pm_value
+ valueoff
;
4008 while (valuelen
-- > 0) {
4018 if (output
> endptr
) {
4019 advise(NULL
, "Internal error: parameter buffer overflow");
4024 if (output
- 2 > endptr
) {
4025 advise(NULL
, "Internal error: parameter buffer overflow");
4036 * Add a parameter to the parameter linked list
4040 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4042 PM pm
= mh_xmalloc(sizeof(*pm
));
4044 memset(pm
, 0, sizeof(*pm
));
4046 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4047 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4050 (*last
)->pm_next
= pm
;
4061 * Either replace a current parameter with a new value, or add the parameter
4062 * to the parameter linked list.
4066 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4070 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4071 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4073 * If nocopy is set, it's assumed that we own both name
4074 * and value. We don't need name, so we discard it now.
4079 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4084 return add_param(first
, last
, name
, value
, nocopy
);
4088 * Retrieve a parameter value from a parameter linked list. If the parameter
4089 * value needs converted to the local character set, do that now.
4093 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4095 while (first
!= NULL
) {
4096 if (strcasecmp(name
, first
->pm_name
) == 0) {
4098 return first
->pm_value
;
4100 return getcpy(get_param_value(first
, replace
));
4102 first
= first
->pm_next
;
4109 * Return a parameter value, converting to the local character set if
4113 char *get_param_value(PM pm
, char replace
)
4115 static char buffer
[4096]; /* I hope no parameters are larger */
4116 size_t bufsize
= sizeof(buffer
);
4121 ICONV_CONST
char *p
;
4122 #else /* HAVE_ICONV */
4124 #endif /* HAVE_ICONV */
4129 * If we don't have a character set indicated, it's assumed to be
4130 * US-ASCII. If it matches our character set, we don't need to convert
4134 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4135 strlen(pm
->pm_charset
))) {
4136 return pm
->pm_value
;
4140 * In this case, we need to convert. If we have iconv support, use
4141 * that. Otherwise, go through and simply replace every non-ASCII
4142 * character with the substitution character.
4147 bufsize
= sizeof(buffer
);
4148 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4150 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4151 if (cd
== (iconv_t
) -1) {
4155 inbytes
= strlen(pm
->pm_value
);
4159 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4160 if (errno
!= EILSEQ
) {
4165 * Reset shift state, substitute our character,
4166 * try to restart conversion.
4169 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4182 for (++p
, --inbytes
;
4183 inbytes
> 0 && (((unsigned char) *q
) & 0xc0) == 0x80;
4202 #endif /* HAVE_ICONV */
4205 * Take everything non-ASCII and substituite the replacement character
4209 bufsize
= sizeof(buffer
);
4210 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4211 if (isascii((unsigned char) *p
) && !iscntrl((unsigned char) *p
))