1 /* mhparse.c -- routines to parse the contents of MIME messages
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
14 #include <h/mhparse.h>
18 #endif /* HAVE_ICONV */
24 extern int rcachesw
; /* mhcachesbr.c */
25 extern int wcachesw
; /* mhcachesbr.c */
27 int checksw
= 0; /* check Content-MD5 field */
30 * These are for mhfixmsg to:
31 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
33 * 2) Suppress the warning about bogus multipart content, and report it.
34 * 3) Suppress the warning about extraneous trailing ';' in header parameter
37 int skip_mp_cte_check
;
38 int suppress_bogus_mp_content_warning
;
40 int suppress_extraneous_trailing_semicolon_warning
;
43 * By default, suppress warning about multiple MIME-Version header fields.
45 int suppress_multiple_mime_version_warning
= 1;
47 /* list of preferred type/subtype pairs, for -prefer */
48 char *preferred_types
[NPREFS
],
49 *preferred_subtypes
[NPREFS
];
54 * Structures for TEXT messages
56 struct k2v SubText
[] = {
57 { "plain", TEXT_PLAIN
},
58 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC-1341 */
59 { "enriched", TEXT_ENRICHED
}, /* defined in RFC-1896 */
60 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
63 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
66 * Structures for MULTIPART messages
68 struct k2v SubMultiPart
[] = {
69 { "mixed", MULTI_MIXED
},
70 { "alternative", MULTI_ALTERNATE
},
71 { "digest", MULTI_DIGEST
},
72 { "parallel", MULTI_PARALLEL
},
73 { "related", MULTI_RELATED
},
74 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
78 * Structures for MESSAGE messages
80 struct k2v SubMessage
[] = {
81 { "rfc822", MESSAGE_RFC822
},
82 { "partial", MESSAGE_PARTIAL
},
83 { "external-body", MESSAGE_EXTERNAL
},
84 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
88 * Structure for APPLICATION messages
90 struct k2v SubApplication
[] = {
91 { "octet-stream", APPLICATION_OCTETS
},
92 { "postscript", APPLICATION_POSTSCRIPT
},
93 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
97 * Mapping of names of CTE types in mhbuild directives
99 static struct k2v EncodingType
[] = {
102 { "q-p", CE_QUOTED
},
103 { "quoted-printable", CE_QUOTED
},
104 { "b64", CE_BASE64
},
105 { "base64", CE_BASE64
},
111 int find_cache (CT
, int, int *, char *, char *, int);
115 int type_ok (CT
, int);
116 void content_error (char *, CT
, char *, ...);
119 void free_encoding (CT
, int);
124 static CT
get_content (FILE *, char *, int);
125 static int get_comment (const char *, const char *, char **, char **);
127 static int InitGeneric (CT
);
128 static int InitText (CT
);
129 static int InitMultiPart (CT
);
130 static void reverse_parts (CT
);
131 static void prefer_parts(CT ct
);
132 static int InitMessage (CT
);
133 static int InitApplication (CT
);
134 static int init_encoding (CT
, OpenCEFunc
);
135 static unsigned long size_encoding (CT
);
136 static int InitBase64 (CT
);
137 static int openBase64 (CT
, char **);
138 static int InitQuoted (CT
);
139 static int openQuoted (CT
, char **);
140 static int Init7Bit (CT
);
141 static int openExternal (CT
, CT
, CE
, char **, int *);
142 static int InitFile (CT
);
143 static int openFile (CT
, char **);
144 static int InitFTP (CT
);
145 static int openFTP (CT
, char **);
146 static int InitMail (CT
);
147 static int openMail (CT
, char **);
148 static int readDigest (CT
, char *);
149 static int get_leftover_mp_content (CT
, int);
150 static int InitURL (CT
);
151 static int openURL (CT
, char **);
152 static int parse_header_attrs (const char *, const char *, char **, PM
*,
154 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
155 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
156 static int get_dispo (char *, CT
, int);
158 struct str2init str2cts
[] = {
159 { "application", CT_APPLICATION
, InitApplication
},
160 { "audio", CT_AUDIO
, InitGeneric
},
161 { "image", CT_IMAGE
, InitGeneric
},
162 { "message", CT_MESSAGE
, InitMessage
},
163 { "multipart", CT_MULTIPART
, InitMultiPart
},
164 { "text", CT_TEXT
, InitText
},
165 { "video", CT_VIDEO
, InitGeneric
},
166 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
167 { NULL
, CT_UNKNOWN
, NULL
},
170 struct str2init str2ces
[] = {
171 { "base64", CE_BASE64
, InitBase64
},
172 { "quoted-printable", CE_QUOTED
, InitQuoted
},
173 { "8bit", CE_8BIT
, Init7Bit
},
174 { "7bit", CE_7BIT
, Init7Bit
},
175 { "binary", CE_BINARY
, Init7Bit
},
176 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
177 { NULL
, CE_UNKNOWN
, NULL
},
181 * NOTE WELL: si_key MUST NOT have value of NOTOK
183 * si_key is 1 if access method is anonymous.
185 struct str2init str2methods
[] = {
186 { "afs", 1, InitFile
},
187 { "anon-ftp", 1, InitFTP
},
188 { "ftp", 0, InitFTP
},
189 { "local-file", 0, InitFile
},
190 { "mail-server", 0, InitMail
},
191 { "url", 0, InitURL
},
197 * Main entry point for parsing a MIME message or file.
198 * It returns the Content structure for the top level
199 * entity in the file.
203 parse_mime (char *file
)
212 bogus_mp_content
= 0;
215 * Check if file is actually standard input
217 if ((is_stdin
= !(strcmp (file
, "-")))) {
218 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
220 advise("mhparse", "unable to create temporary file in %s",
224 file
= mh_xstrdup(tfile
);
226 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
227 if (fwrite(buffer
, 1, n
, fp
) != n
) {
228 (void) m_unlink (file
);
229 advise (file
, "error copying to temporary file");
235 if (ferror (stdin
)) {
236 (void) m_unlink (file
);
237 advise ("stdin", "error reading");
241 (void) m_unlink (file
);
242 advise (file
, "error writing");
245 fseek (fp
, 0L, SEEK_SET
);
246 } else if (stat (file
, &statbuf
) == NOTOK
) {
247 advise (file
, "unable to stat");
249 } else if (S_ISDIR(statbuf
.st_mode
)) {
250 /* Don't try to parse a directory. */
251 inform("%s is a directory", file
);
253 } else if ((fp
= fopen (file
, "r")) == NULL
) {
254 advise (file
, "unable to read");
258 if (!(ct
= get_content (fp
, file
, 1))) {
260 (void) m_unlink (file
);
261 inform("unable to decode %s", file
);
266 ct
->c_unlink
= 1; /* temp file to remove */
270 if (ct
->c_end
== 0L) {
271 fseek (fp
, 0L, SEEK_END
);
272 ct
->c_end
= ftell (fp
);
275 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
287 * Main routine for reading/parsing the headers
288 * of a message content.
290 * toplevel = 1 # we are at the top level of the message
291 * toplevel = 0 # we are inside message type or multipart type
292 * # other than multipart/digest
293 * toplevel = -1 # we are inside multipart/digest
294 * NB: on failure we will fclose(in)!
298 get_content (FILE *in
, char *file
, int toplevel
)
301 char buf
[BUFSIZ
], name
[NAMESZ
];
305 m_getfld_state_t gstate
= 0;
307 /* allocate the content structure */
310 ct
->c_file
= add (file
, NULL
);
311 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
314 * Parse the header fields for this
315 * content into a linked list.
317 m_getfld_track_filepos (&gstate
, in
);
318 for (compnum
= 1;;) {
319 int bufsz
= sizeof buf
;
320 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
325 /* get copies of the buffers */
326 np
= mh_xstrdup(name
);
327 vp
= mh_xstrdup(buf
);
329 /* if necessary, get rest of field */
330 while (state
== FLDPLUS
) {
332 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
333 vp
= add (buf
, vp
); /* add to previous value */
336 /* Now add the header data to the list */
337 add_header (ct
, np
, vp
);
339 /* continue, to see if this isn't the last header field */
340 ct
->c_begin
= ftell (in
) + 1;
344 if (name
[0] == ':') {
345 /* Special case: no blank line between header and body. The
346 file position indicator is on the newline at the end of the
347 line, but it needs to be one prior to the beginning of the
348 line. So subtract the length of the line, bufsz, plus 1. */
349 ct
->c_begin
= ftell (in
) - (bufsz
+ 1);
351 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
356 ct
->c_begin
= ftell (in
);
361 adios (NULL
, "message format error in component #%d", compnum
);
364 adios (NULL
, "getfld() returned %d", state
);
367 /* break out of the loop */
370 m_getfld_state_destroy (&gstate
);
373 * Read the content headers. We will parse the
374 * MIME related header fields into their various
375 * structures and set internal flags related to
376 * content type/subtype, etc.
379 hp
= ct
->c_first_hf
; /* start at first header field */
381 /* Get MIME-Version field */
382 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
387 vrsn
= add (hp
->value
, NULL
);
389 /* Now, cleanup this field */
392 while (isspace ((unsigned char) *cp
))
394 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
396 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
397 if (!isspace ((unsigned char) *dp
))
401 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
404 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
407 for (dp
= cp
; istoken (*dp
); dp
++)
411 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
414 inform("message %s has unknown value for %s: field (%s), continuing...",
415 ct
->c_file
, VRSN_FIELD
, cp
);
420 if (! suppress_multiple_mime_version_warning
)
421 inform("message %s has multiple %s: fields",
422 ct
->c_file
, VRSN_FIELD
);
426 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
427 /* Get Content-Type field */
428 struct str2init
*s2i
;
429 CI ci
= &ct
->c_ctinfo
;
431 /* Check if we've already seen a Content-Type header */
433 inform("message %s has multiple %s: fields",
434 ct
->c_file
, TYPE_FIELD
);
438 /* Parse the Content-Type field */
439 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
443 * Set the Init function and the internal
444 * flag for this content type.
446 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
447 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
449 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
451 ct
->c_type
= s2i
->si_val
;
452 ct
->c_ctinitfnx
= s2i
->si_init
;
454 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
455 /* Get Content-Transfer-Encoding field */
457 struct str2init
*s2i
;
460 * Check if we've already seen the
461 * Content-Transfer-Encoding field
464 inform("message %s has multiple %s: fields",
465 ct
->c_file
, ENCODING_FIELD
);
469 /* get copy of this field */
470 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
472 while (isspace ((unsigned char) *cp
))
474 for (dp
= cp
; istoken (*dp
); dp
++)
480 * Find the internal flag and Init function
481 * for this transfer encoding.
483 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
484 if (!strcasecmp (cp
, s2i
->si_key
))
486 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
489 ct
->c_encoding
= s2i
->si_val
;
491 /* Call the Init function for this encoding */
492 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
495 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
496 /* Get Content-MD5 field */
502 if (ct
->c_digested
) {
503 inform("message %s has multiple %s: fields",
504 ct
->c_file
, MD5_FIELD
);
508 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
510 while (isspace ((unsigned char) *cp
))
512 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
514 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
515 if (!isspace ((unsigned char) *dp
))
519 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
522 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
527 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
535 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
536 /* Get Content-ID field */
537 ct
->c_id
= add (hp
->value
, ct
->c_id
);
539 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
540 /* Get Content-Description field */
541 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
543 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
544 /* Get Content-Disposition field */
545 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
550 hp
= hp
->next
; /* next header field */
554 * Check if we saw a Content-Type field.
555 * If not, then assign a default value for
556 * it, and the Init function.
560 * If we are inside a multipart/digest message,
561 * so default type is message/rfc822
564 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
566 ct
->c_type
= CT_MESSAGE
;
567 ct
->c_ctinitfnx
= InitMessage
;
570 * Else default type is text/plain
572 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
574 ct
->c_type
= CT_TEXT
;
575 ct
->c_ctinitfnx
= InitText
;
579 /* Use default Transfer-Encoding, if necessary */
581 ct
->c_encoding
= CE_7BIT
;
594 * small routine to add header field to list
598 add_header (CT ct
, char *name
, char *value
)
602 /* allocate header field structure */
605 /* link data into header structure */
610 /* link header structure into the list */
611 if (ct
->c_first_hf
== NULL
) {
612 ct
->c_first_hf
= hp
; /* this is the first */
615 ct
->c_last_hf
->next
= hp
; /* add it to the end */
624 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
625 * directives. Fills in the information of the CTinfo structure.
628 get_ctinfo (char *cp
, CT ct
, int magic
)
637 /* store copy of Content-Type line */
638 cp
= ct
->c_ctline
= add (cp
, NULL
);
640 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
643 /* change newlines to spaces */
644 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
647 /* trim trailing spaces */
648 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
649 if (!isspace ((unsigned char) *dp
))
654 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, 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_type
= mh_xstrdup(cp
); /* store content type */
667 inform("invalid %s: field in message %s (empty type)",
668 TYPE_FIELD
, ct
->c_file
);
671 to_lower(ci
->ci_type
);
673 while (isspace ((unsigned char) *cp
))
676 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
677 &ci
->ci_comment
) == NOTOK
)
682 ci
->ci_subtype
= mh_xstrdup("");
687 while (isspace ((unsigned char) *cp
))
690 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
691 &ci
->ci_comment
) == NOTOK
)
694 for (dp
= cp
; istoken (*dp
); dp
++)
697 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
700 if (!*ci
->ci_subtype
) {
701 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
702 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
705 to_lower(ci
->ci_subtype
);
708 while (isspace ((unsigned char) *cp
))
711 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
712 &ci
->ci_comment
) == NOTOK
)
715 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
716 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
717 &ci
->ci_comment
)) != OK
) {
718 return status
== NOTOK
? NOTOK
: OK
;
722 * Get any <Content-Id> given in buffer
724 if (magic
&& *cp
== '<') {
727 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
728 inform("invalid ID in message %s", ct
->c_file
);
734 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
740 while (isspace ((unsigned char) *cp
))
745 * Get any [Content-Description] given in buffer.
747 if (magic
&& *cp
== '[') {
749 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
753 inform("invalid description in message %s", ct
->c_file
);
761 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
767 while (isspace ((unsigned char) *cp
))
772 * Get any {Content-Disposition} given in buffer.
774 if (magic
&& *cp
== '{') {
776 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
780 inform("invalid disposition in message %s", ct
->c_file
);
788 if (get_dispo(cp
, ct
, 1) != OK
)
794 while (isspace ((unsigned char) *cp
))
799 * Get any extension directives (right now just the content transfer
800 * encoding, but maybe others) that we care about.
803 if (magic
&& *cp
== '*') {
805 * See if it's a CTE we match on
810 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
814 inform("invalid null transfer encoding specification");
821 ct
->c_reqencoding
= CE_UNKNOWN
;
823 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
824 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
825 ct
->c_reqencoding
= kv
->kv_value
;
830 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
831 inform("invalid CTE specification: \"%s\"", dp
);
835 while (isspace ((unsigned char) *cp
))
840 * Check if anything is left over
844 ci
->ci_magic
= mh_xstrdup(cp
);
846 /* If there is a Content-Disposition header and it doesn't
847 have a *filename=, extract it from the magic contents.
848 The r1bindex call skips any leading directory
850 if (ct
->c_dispo_type
&&
851 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
852 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
853 r1bindex(ci
->ci_magic
, '/'), 0);
857 inform("extraneous information in message %s's %s: field\n%*s(%s)",
858 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
866 * Parse out a Content-Disposition header. A lot of this is cribbed from
870 get_dispo (char *cp
, CT ct
, int buildflag
)
872 char *dp
, *dispoheader
;
877 * Save the whole copy of the Content-Disposition header, unless we're
878 * processing a mhbuild directive. A NULL c_dispo will be a flag to
879 * mhbuild that the disposition header needs to be generated at that
883 dispoheader
= cp
= add(cp
, NULL
);
885 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
888 /* change newlines to spaces */
889 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
892 /* trim trailing spaces */
893 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
894 if (!isspace ((unsigned char) *dp
))
899 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
901 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
907 for (dp
= cp
; istoken (*dp
); dp
++)
910 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
913 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
916 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
917 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
919 if (status
== NOTOK
) {
924 inform("extraneous information in message %s's %s: field\n%*s(%s)",
925 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
931 ct
->c_dispo
= dispoheader
;
938 get_comment (const char *filename
, const char *fieldname
, char **ap
,
943 char c
, buffer
[BUFSIZ
], *dp
;
953 inform("invalid comment in message %s's %s: field",
954 filename
, fieldname
);
959 if ((c
= *cp
++) == '\0')
982 if ((dp
= *commentp
)) {
983 *commentp
= concat (dp
, " ", buffer
, NULL
);
986 *commentp
= mh_xstrdup(buffer
);
990 while (isspace ((unsigned char) *cp
))
1001 * Handles content types audio, image, and video.
1002 * There's not much to do right here.
1010 return OK
; /* not much to do here */
1021 char buffer
[BUFSIZ
];
1026 CI ci
= &ct
->c_ctinfo
;
1028 /* check for missing subtype */
1029 if (!*ci
->ci_subtype
)
1030 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1033 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1035 /* allocate text character set structure */
1037 ct
->c_ctparams
= (void *) t
;
1039 /* scan for charset parameter */
1040 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1041 if (!strcasecmp (pm
->pm_name
, "charset"))
1044 /* check if content specified a character set */
1046 chset
= pm
->pm_value
;
1047 t
->tx_charset
= CHARSET_SPECIFIED
;
1049 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1053 * If we can not handle character set natively,
1054 * then check profile for string to modify the
1055 * terminal or display method.
1057 * termproc is for mhshow, though mhlist -debug prints it, too.
1059 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1060 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1061 if ((cp
= context_find (buffer
)))
1062 ct
->c_termproc
= mh_xstrdup(cp
);
1074 InitMultiPart (CT ct
)
1084 struct multipart
*m
;
1085 struct part
*part
, **next
;
1086 CI ci
= &ct
->c_ctinfo
;
1091 * The encoding for multipart messages must be either
1092 * 7bit, 8bit, or binary (per RFC2045).
1094 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1095 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1096 /* Copy the Content-Transfer-Encoding header field body so we can
1097 remove any trailing whitespace and leading blanks from it. */
1098 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1100 bp
= cte
+ strlen (cte
) - 1;
1101 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1102 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1104 inform("\"%s/%s\" type in message %s must be encoded in\n"
1105 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1106 "mhfixmsg -fixcte can fix it, or\n"
1107 "manually edit the file and change the \"%s\"\n"
1108 "Content-Transfer-Encoding to one of those. For now, continuing...",
1109 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1116 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1119 * Check for "boundary" parameter, which is
1120 * required for multipart messages.
1123 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1124 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1130 /* complain if boundary parameter is missing */
1132 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1133 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1137 /* allocate primary structure for multipart info */
1139 ct
->c_ctparams
= (void *) m
;
1141 /* check if boundary parameter contains only whitespace characters */
1142 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1145 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1146 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1150 /* remove trailing whitespace from boundary parameter */
1151 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1152 if (!isspace ((unsigned char) *dp
))
1156 /* record boundary separators */
1157 m
->mp_start
= concat (bp
, "\n", NULL
);
1158 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1160 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1161 advise (ct
->c_file
, "unable to open for reading");
1165 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1167 next
= &m
->mp_parts
;
1171 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1176 if (bufp
[0] != '-' || bufp
[1] != '-')
1179 if (strcmp (bufp
+ 2, m
->mp_start
))
1184 next
= &part
->mp_next
;
1186 if (!(p
= get_content (fp
, ct
->c_file
,
1187 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1195 fseek (fp
, pos
, SEEK_SET
);
1198 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1202 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1203 if (p
->c_end
< p
->c_begin
)
1204 p
->c_begin
= p
->c_end
;
1209 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1214 if (! suppress_bogus_mp_content_warning
) {
1215 inform("bogus multipart content in message %s", ct
->c_file
);
1217 bogus_mp_content
= 1;
1219 if (!inout
&& part
) {
1221 p
->c_end
= ct
->c_end
;
1223 if (p
->c_begin
>= p
->c_end
) {
1224 for (next
= &m
->mp_parts
; *next
!= part
;
1225 next
= &((*next
)->mp_next
))
1234 /* reverse the order of the parts for multipart/alternative */
1235 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1241 * label all subparts with part number, and
1242 * then initialize the content of the subpart.
1247 char partnam
[BUFSIZ
];
1250 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1251 pp
= partnam
+ strlen (partnam
);
1256 for (part
= m
->mp_parts
, partnum
= 1; part
;
1257 part
= part
->mp_next
, partnum
++) {
1260 sprintf (pp
, "%d", partnum
);
1261 p
->c_partno
= mh_xstrdup(partnam
);
1263 /* initialize the content of the subparts */
1264 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1273 get_leftover_mp_content (ct
, 1);
1274 get_leftover_mp_content (ct
, 0);
1284 * reverse the order of the parts of a multipart/alternative,
1285 * presumably to put the "most favored" alternative first, for
1286 * ease of choosing/displaying it later on. from a mail message on
1287 * nmh-workers, from kenh:
1288 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1289 * see code in mhn that did the same thing... Acccording to the RCS
1290 * logs, that code was around from the initial checkin of mhn.c by
1291 * John Romine in 1992, which is as far back as we have."
1294 reverse_parts (CT ct
)
1296 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1300 /* Reverse the order of its parts by walking the mp_parts list
1301 and pushing each node to the front. */
1302 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1303 next
= part
->mp_next
;
1304 part
->mp_next
= m
->mp_parts
;
1310 move_preferred_part (CT ct
, char *type
, char *subtype
)
1312 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1313 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1317 /* move the matching part(s) to the head of the list: walk the
1318 * list of parts, move matching parts to a new list (maintaining
1319 * their order), and finally, concatenate the old list onto the
1326 head
->mp_next
= m
->mp_parts
;
1327 nhead
->mp_next
= NULL
;
1331 part
= head
->mp_next
;
1332 while (part
!= NULL
) {
1333 ci
= &part
->mp_part
->c_ctinfo
;
1334 if (!strcasecmp(ci
->ci_type
, type
) &&
1335 (!subtype
|| !strcasecmp(ci
->ci_subtype
, subtype
))) {
1336 prev
->mp_next
= part
->mp_next
;
1337 part
->mp_next
= NULL
;
1338 ntail
->mp_next
= part
;
1340 part
= prev
->mp_next
;
1343 part
= prev
->mp_next
;
1346 ntail
->mp_next
= head
->mp_next
;
1347 m
->mp_parts
= nhead
->mp_next
;
1352 * move parts that match the user's preferences (-prefer) to the head
1353 * of the line. process preferences in reverse so first one given
1354 * ends up first in line
1360 for (i
= npreferred
-1; i
>= 0; i
--)
1361 move_preferred_part(ct
, preferred_types
[i
], preferred_subtypes
[i
]);
1366 /* parse_mime() arranges alternates in reverse (priority) order. This
1367 function can be used to reverse them back. This will put, for
1368 example, a text/plain part before a text/html part in a
1369 multipart/alternative part, for example, where it belongs. */
1371 reverse_alternative_parts (CT ct
) {
1372 if (ct
->c_type
== CT_MULTIPART
) {
1373 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1376 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1380 /* And call recursively on each part of a multipart. */
1381 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1382 reverse_alternative_parts (part
->mp_part
);
1395 CI ci
= &ct
->c_ctinfo
;
1397 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1398 inform("\"%s/%s\" type in message %s should be encoded in "
1399 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1404 /* check for missing subtype */
1405 if (!*ci
->ci_subtype
)
1406 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1409 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1411 switch (ct
->c_subtype
) {
1412 case MESSAGE_RFC822
:
1415 case MESSAGE_PARTIAL
:
1421 ct
->c_ctparams
= (void *) p
;
1423 /* scan for parameters "id", "number", and "total" */
1424 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1425 if (!strcasecmp (pm
->pm_name
, "id")) {
1426 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1429 if (!strcasecmp (pm
->pm_name
, "number")) {
1430 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1431 || p
->pm_partno
< 1) {
1433 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1434 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1435 ct
->c_file
, TYPE_FIELD
);
1440 if (!strcasecmp (pm
->pm_name
, "total")) {
1441 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1450 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1451 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1452 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1458 case MESSAGE_EXTERNAL
:
1466 ct
->c_ctparams
= (void *) e
;
1469 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1470 advise (ct
->c_file
, "unable to open for reading");
1474 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1476 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1484 p
->c_ceopenfnx
= NULL
;
1485 if ((exresult
= params_external (ct
, 0)) != NOTOK
1486 && p
->c_ceopenfnx
== openMail
) {
1490 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1492 content_error (NULL
, ct
,
1493 "empty body for access-type=mail-server");
1497 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1498 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1500 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1502 adios ("failed", "fread");
1505 adios (NULL
, "unexpected EOF from fread");
1508 bp
+= cc
, size
-= cc
;
1515 p
->c_end
= p
->c_begin
;
1520 if (exresult
== NOTOK
)
1522 if (e
->eb_flags
== NOTOK
)
1525 switch (p
->c_type
) {
1530 if (p
->c_subtype
!= MESSAGE_RFC822
)
1534 e
->eb_partno
= ct
->c_partno
;
1536 (*p
->c_ctinitfnx
) (p
);
1551 params_external (CT ct
, int composing
)
1554 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1555 CI ci
= &ct
->c_ctinfo
;
1557 ct
->c_ceopenfnx
= NULL
;
1558 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1559 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1560 struct str2init
*s2i
;
1561 CT p
= e
->eb_content
;
1563 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1564 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1567 e
->eb_access
= pm
->pm_value
;
1568 e
->eb_flags
= NOTOK
;
1569 p
->c_encoding
= CE_EXTERNAL
;
1572 e
->eb_access
= s2i
->si_key
;
1573 e
->eb_flags
= s2i
->si_val
;
1574 p
->c_encoding
= CE_EXTERNAL
;
1576 /* Call the Init function for this external type */
1577 if ((*s2i
->si_init
)(p
) == NOTOK
)
1581 if (!strcasecmp (pm
->pm_name
, "name")) {
1582 e
->eb_name
= pm
->pm_value
;
1585 if (!strcasecmp (pm
->pm_name
, "permission")) {
1586 e
->eb_permission
= pm
->pm_value
;
1589 if (!strcasecmp (pm
->pm_name
, "site")) {
1590 e
->eb_site
= pm
->pm_value
;
1593 if (!strcasecmp (pm
->pm_name
, "directory")) {
1594 e
->eb_dir
= pm
->pm_value
;
1597 if (!strcasecmp (pm
->pm_name
, "mode")) {
1598 e
->eb_mode
= pm
->pm_value
;
1601 if (!strcasecmp (pm
->pm_name
, "size")) {
1602 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1605 if (!strcasecmp (pm
->pm_name
, "server")) {
1606 e
->eb_server
= pm
->pm_value
;
1609 if (!strcasecmp (pm
->pm_name
, "subject")) {
1610 e
->eb_subject
= pm
->pm_value
;
1613 if (!strcasecmp (pm
->pm_name
, "url")) {
1615 * According to RFC 2017, we have to remove all whitespace from
1619 char *u
, *p
= pm
->pm_value
;
1620 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1622 for (; *p
!= '\0'; p
++) {
1623 if (! isspace((unsigned char) *p
))
1630 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1631 e
->eb_body
= getcpy (pm
->pm_value
);
1636 if (!e
->eb_access
) {
1637 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1638 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1651 InitApplication (CT ct
)
1653 CI ci
= &ct
->c_ctinfo
;
1656 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1663 * TRANSFER ENCODINGS
1667 init_encoding (CT ct
, OpenCEFunc openfnx
)
1669 ct
->c_ceopenfnx
= openfnx
;
1670 ct
->c_ceclosefnx
= close_encoding
;
1671 ct
->c_cesizefnx
= size_encoding
;
1678 close_encoding (CT ct
)
1680 CE ce
= &ct
->c_cefile
;
1689 static unsigned long
1690 size_encoding (CT ct
)
1695 CE ce
= &ct
->c_cefile
;
1698 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1699 return (long) st
.st_size
;
1702 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1703 return (long) st
.st_size
;
1707 if (ct
->c_encoding
== CE_EXTERNAL
)
1708 return (ct
->c_end
- ct
->c_begin
);
1711 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1712 return (ct
->c_end
- ct
->c_begin
);
1714 if (fstat (fd
, &st
) != NOTOK
)
1715 size
= (long) st
.st_size
;
1719 (*ct
->c_ceclosefnx
) (ct
);
1731 return init_encoding (ct
, openBase64
);
1736 openBase64 (CT ct
, char **file
)
1739 int fd
, own_ct_fp
= 0;
1740 char *cp
, *buffer
= NULL
;
1741 /* sbeck -- handle suffixes */
1743 CE ce
= &ct
->c_cefile
;
1744 unsigned char *decoded
;
1746 unsigned char digest
[16];
1749 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1754 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1755 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1761 if (*file
== NULL
) {
1764 ce
->ce_file
= mh_xstrdup(*file
);
1768 /* sbeck@cise.ufl.edu -- handle suffixes */
1770 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1771 if (ce
->ce_unlink
) {
1772 /* Create temporary file with filename extension. */
1773 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1774 adios(NULL
, "unable to create temporary file in %s",
1778 ce
->ce_file
= add (cp
, ce
->ce_file
);
1780 } else if (*file
== NULL
) {
1782 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1783 adios(NULL
, "unable to create temporary file in %s",
1786 ce
->ce_file
= mh_xstrdup(tempfile
);
1789 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1790 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1794 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1795 adios (NULL
, "internal error(1)");
1797 buffer
= mh_xmalloc (len
+ 1);
1800 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1801 content_error (ct
->c_file
, ct
, "unable to open for reading");
1807 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1810 switch (cc
= read (fd
, cp
, len
)) {
1812 content_error (ct
->c_file
, ct
, "error reading from");
1816 content_error (NULL
, ct
, "premature eof");
1827 /* decodeBase64() requires null-terminated input. */
1830 if (decodeBase64 (buffer
, &decoded
, &decoded_len
, ct
->c_type
== CT_TEXT
,
1831 ct
->c_digested
? digest
: NULL
) == OK
) {
1833 unsigned char *decoded_p
= decoded
;
1834 for (i
= 0; i
< decoded_len
; ++i
) {
1835 putc (*decoded_p
++, ce
->ce_fp
);
1838 if (ferror (ce
->ce_fp
)) {
1839 content_error (ce
->ce_file
, ct
, "error writing to");
1843 if (ct
->c_digested
) {
1844 if (memcmp(digest
, ct
->c_digest
,
1845 sizeof(digest
) / sizeof(digest
[0]))) {
1846 content_error (NULL
, ct
,
1847 "content integrity suspect (digest mismatch) -- continuing");
1850 fprintf (stderr
, "content integrity confirmed\n");
1858 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1860 if (fflush (ce
->ce_fp
)) {
1861 content_error (ce
->ce_file
, ct
, "error writing to");
1865 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1868 *file
= ce
->ce_file
;
1874 return fileno (ce
->ce_fp
);
1881 free_encoding (ct
, 0);
1891 static char hex2nib
[0x80] = {
1892 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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,
1896 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1897 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1898 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1899 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1900 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1901 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1903 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1904 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1905 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1914 return init_encoding (ct
, openQuoted
);
1919 openQuoted (CT ct
, char **file
)
1921 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1927 CE ce
= &ct
->c_cefile
;
1928 /* sbeck -- handle suffixes */
1933 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1938 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1939 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1945 if (*file
== NULL
) {
1948 ce
->ce_file
= mh_xstrdup(*file
);
1952 /* sbeck@cise.ufl.edu -- handle suffixes */
1954 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1955 if (ce
->ce_unlink
) {
1956 /* Create temporary file with filename extension. */
1957 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1958 adios(NULL
, "unable to create temporary file in %s",
1962 ce
->ce_file
= add (cp
, ce
->ce_file
);
1964 } else if (*file
== NULL
) {
1966 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1967 adios(NULL
, "unable to create temporary file in %s",
1970 ce
->ce_file
= mh_xstrdup(tempfile
);
1973 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1974 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1978 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1979 adios (NULL
, "internal error(2)");
1982 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1983 content_error (ct
->c_file
, ct
, "unable to open for reading");
1989 if ((digested
= ct
->c_digested
))
1990 MD5Init (&mdContext
);
1997 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1999 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2000 content_error (NULL
, ct
, "premature eof");
2004 if ((cc
= gotlen
) > len
)
2008 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2009 if (!isspace ((unsigned char) *ep
))
2013 for (; cp
< ep
; cp
++) {
2015 /* in an escape sequence */
2017 /* at byte 1 of an escape sequence */
2018 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2019 /* next is byte 2 */
2022 /* at byte 2 of an escape sequence */
2024 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2025 putc (mask
, ce
->ce_fp
);
2027 MD5Update (&mdContext
, &mask
, 1);
2028 if (ferror (ce
->ce_fp
)) {
2029 content_error (ce
->ce_file
, ct
, "error writing to");
2032 /* finished escape sequence; next may be literal or a new
2033 * escape sequence */
2036 /* on to next byte */
2040 /* not in an escape sequence */
2042 /* starting an escape sequence, or invalid '='? */
2043 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2044 /* "=\n" soft line break, eat the \n */
2048 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2049 /* We don't have 2 bytes left, so this is an invalid
2050 * escape sequence; just show the raw bytes (below). */
2051 } else if (isxdigit ((unsigned char) cp
[1]) &&
2052 isxdigit ((unsigned char) cp
[2])) {
2053 /* Next 2 bytes are hex digits, making this a valid escape
2054 * sequence; let's decode it (above). */
2058 /* One or both of the next 2 is out of range, making this
2059 * an invalid escape sequence; just show the raw bytes
2064 /* Just show the raw byte. */
2065 putc (*cp
, ce
->ce_fp
);
2068 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2070 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2073 if (ferror (ce
->ce_fp
)) {
2074 content_error (ce
->ce_file
, ct
, "error writing to");
2080 content_error (NULL
, ct
,
2081 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2085 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2087 if (fflush (ce
->ce_fp
)) {
2088 content_error (ce
->ce_file
, ct
, "error writing to");
2093 unsigned char digest
[16];
2095 MD5Final (digest
, &mdContext
);
2096 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2097 sizeof(digest
) / sizeof(digest
[0])))
2098 content_error (NULL
, ct
,
2099 "content integrity suspect (digest mismatch) -- continuing");
2102 fprintf (stderr
, "content integrity confirmed\n");
2105 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2108 *file
= ce
->ce_file
;
2114 return fileno (ce
->ce_fp
);
2117 free_encoding (ct
, 0);
2134 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2137 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2143 open7Bit (CT ct
, char **file
)
2145 int cc
, fd
, len
, own_ct_fp
= 0;
2146 char buffer
[BUFSIZ
];
2147 /* sbeck -- handle suffixes */
2150 CE ce
= &ct
->c_cefile
;
2153 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2158 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2159 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2165 if (*file
== NULL
) {
2168 ce
->ce_file
= mh_xstrdup(*file
);
2172 /* sbeck@cise.ufl.edu -- handle suffixes */
2174 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
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
= mh_xstrdup(tempfile
);
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 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2263 advise ("open7Bit", "fwrite");
2265 if (ferror (ce
->ce_fp
)) {
2266 content_error (ce
->ce_file
, ct
, "error writing to");
2271 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2273 if (fflush (ce
->ce_fp
)) {
2274 content_error (ce
->ce_file
, ct
, "error writing to");
2278 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2281 *file
= ce
->ce_file
;
2286 return fileno (ce
->ce_fp
);
2289 free_encoding (ct
, 0);
2303 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2305 char cachefile
[BUFSIZ
];
2308 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2313 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2314 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2320 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2321 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2322 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2323 ce
->ce_file
= mh_xstrdup(cachefile
);
2327 admonish (cachefile
, "unable to fopen for reading");
2330 *fd
= fileno (ce
->ce_fp
);
2334 *file
= ce
->ce_file
;
2335 *fd
= fileno (ce
->ce_fp
);
2346 return init_encoding (ct
, openFile
);
2351 openFile (CT ct
, char **file
)
2354 char cachefile
[BUFSIZ
];
2355 struct exbody
*e
= ct
->c_ctexbody
;
2356 CE ce
= &ct
->c_cefile
;
2358 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2370 content_error (NULL
, ct
, "missing name parameter");
2374 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2377 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2378 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2382 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2383 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2384 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2388 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2389 if ((fp
= fopen (cachefile
, "w"))) {
2391 char buffer
[BUFSIZ
];
2392 FILE *gp
= ce
->ce_fp
;
2394 fseek (gp
, 0L, SEEK_SET
);
2396 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2398 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2399 advise ("openFile", "fwrite");
2404 admonish (ce
->ce_file
, "error reading");
2405 (void) m_unlink (cachefile
);
2409 admonish (cachefile
, "error writing");
2410 (void) m_unlink (cachefile
);
2417 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2418 *file
= ce
->ce_file
;
2419 return fileno (ce
->ce_fp
);
2429 return init_encoding (ct
, openFTP
);
2434 openFTP (CT ct
, char **file
)
2436 int cachetype
, caching
, fd
;
2438 char *bp
, *ftp
, *user
, *pass
;
2439 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2441 CE ce
= &ct
->c_cefile
;
2442 static char *username
= NULL
;
2443 static char *password
= NULL
;
2447 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2453 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2464 if (!e
->eb_name
|| !e
->eb_site
) {
2465 content_error (NULL
, ct
, "missing %s parameter",
2466 e
->eb_name
? "site": "name");
2470 /* Get the buffer ready to go */
2472 buflen
= sizeof(buffer
);
2475 * Construct the query message for user
2477 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2483 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2489 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2490 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2495 if (e
->eb_size
> 0) {
2496 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2501 snprintf (bp
, buflen
, "? ");
2504 * Now, check the answer
2506 if (!read_yes_or_no_if_tty (buffer
))
2511 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2515 ruserpass (e
->eb_site
, &username
, &password
, 0);
2520 ce
->ce_unlink
= (*file
== NULL
);
2522 cachefile
[0] = '\0';
2523 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2524 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2525 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2526 if (*file
== NULL
) {
2533 ce
->ce_file
= mh_xstrdup(*file
);
2535 ce
->ce_file
= mh_xstrdup(cachefile
);
2538 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2539 adios(NULL
, "unable to create temporary file in %s",
2542 ce
->ce_file
= mh_xstrdup(tempfile
);
2545 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2546 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2551 int child_id
, i
, vecp
;
2555 vec
[vecp
++] = r1bindex (ftp
, '/');
2556 vec
[vecp
++] = e
->eb_site
;
2559 vec
[vecp
++] = e
->eb_dir
;
2560 vec
[vecp
++] = e
->eb_name
;
2561 vec
[vecp
++] = ce
->ce_file
,
2562 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2563 ? "ascii" : "binary";
2568 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2572 adios ("fork", "unable to");
2576 close (fileno (ce
->ce_fp
));
2578 fprintf (stderr
, "unable to exec ");
2584 if (pidXwait (child_id
, NULL
)) {
2585 username
= password
= NULL
;
2595 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2600 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2601 if ((fp
= fopen (cachefile
, "w"))) {
2603 FILE *gp
= ce
->ce_fp
;
2605 fseek (gp
, 0L, SEEK_SET
);
2607 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2609 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2610 advise ("openFTP", "fwrite");
2615 admonish (ce
->ce_file
, "error reading");
2616 (void) m_unlink (cachefile
);
2620 admonish (cachefile
, "error writing");
2621 (void) m_unlink (cachefile
);
2629 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2630 *file
= ce
->ce_file
;
2631 return fileno (ce
->ce_fp
);
2642 return init_encoding (ct
, openMail
);
2647 openMail (CT ct
, char **file
)
2649 int child_id
, fd
, i
, vecp
;
2651 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2652 struct exbody
*e
= ct
->c_ctexbody
;
2653 CE ce
= &ct
->c_cefile
;
2655 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2666 if (!e
->eb_server
) {
2667 content_error (NULL
, ct
, "missing server parameter");
2671 /* Get buffer ready to go */
2673 buflen
= sizeof(buffer
);
2675 /* Now, construct query message */
2676 snprintf (bp
, buflen
, "Retrieve content");
2682 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2688 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2690 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2692 /* Now, check answer */
2693 if (!read_yes_or_no_if_tty (buffer
))
2697 vec
[vecp
++] = r1bindex (mailproc
, '/');
2698 vec
[vecp
++] = e
->eb_server
;
2699 vec
[vecp
++] = "-subject";
2700 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2701 vec
[vecp
++] = "-body";
2702 vec
[vecp
++] = e
->eb_body
;
2705 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2709 advise ("fork", "unable to");
2713 execvp (mailproc
, vec
);
2714 fprintf (stderr
, "unable to exec ");
2720 if (pidXwait (child_id
, NULL
) == OK
)
2721 inform("request sent");
2725 if (*file
== NULL
) {
2727 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2728 adios(NULL
, "unable to create temporary file in %s",
2731 ce
->ce_file
= mh_xstrdup(tempfile
);
2734 ce
->ce_file
= mh_xstrdup(*file
);
2738 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2739 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2743 /* showproc is for mhshow and mhstore, though mhlist -debug
2744 * prints it, too. */
2745 mh_xfree(ct
->c_showproc
);
2746 ct
->c_showproc
= mh_xstrdup("true");
2748 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2749 *file
= ce
->ce_file
;
2750 return fileno (ce
->ce_fp
);
2761 return init_encoding (ct
, openURL
);
2766 openURL (CT ct
, char **file
)
2768 struct exbody
*e
= ct
->c_ctexbody
;
2769 CE ce
= &ct
->c_cefile
;
2770 char *urlprog
, *program
;
2771 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2772 int fd
, caching
, cachetype
;
2773 struct msgs_array args
= { 0, 0, NULL
};
2776 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2780 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2784 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2796 content_error(NULL
, ct
, "missing url parameter");
2800 ce
->ce_unlink
= (*file
== NULL
);
2802 cachefile
[0] = '\0';
2804 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2805 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2806 if (*file
== NULL
) {
2813 ce
->ce_file
= mh_xstrdup(*file
);
2815 ce
->ce_file
= mh_xstrdup(cachefile
);
2818 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2819 adios(NULL
, "unable to create temporary file in %s",
2822 ce
->ce_file
= mh_xstrdup(tempfile
);
2825 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2826 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2830 switch (child_id
= fork()) {
2832 adios ("fork", "unable to");
2836 argsplit_msgarg(&args
, urlprog
, &program
);
2837 app_msgarg(&args
, e
->eb_url
);
2838 app_msgarg(&args
, NULL
);
2839 dup2(fileno(ce
->ce_fp
), 1);
2840 close(fileno(ce
->ce_fp
));
2841 execvp(program
, args
.msgs
);
2842 fprintf(stderr
, "Unable to exec ");
2848 if (pidXwait(child_id
, NULL
)) {
2856 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2861 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2862 if ((fp
= fopen(cachefile
, "w"))) {
2864 FILE *gp
= ce
->ce_fp
;
2866 fseeko(gp
, 0, SEEK_SET
);
2868 while ((cc
= fread(buffer
, sizeof(*buffer
),
2869 sizeof(buffer
), gp
)) > 0)
2870 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2871 advise ("openURL", "fwrite");
2877 admonish(ce
->ce_file
, "error reading");
2878 (void) m_unlink (cachefile
);
2885 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2886 *file
= ce
->ce_file
;
2892 * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest. It
2893 * has to be base64 decoded.
2896 readDigest (CT ct
, char *cp
)
2898 unsigned char *digest
;
2901 if (decodeBase64 (cp
, &digest
, &len
, 0, NULL
) == OK
) {
2902 const size_t maxlen
= sizeof ct
->c_digest
/ sizeof ct
->c_digest
[0];
2904 if (strlen ((char *) digest
) <= maxlen
) {
2905 memcpy (ct
->c_digest
, digest
, maxlen
);
2910 fprintf (stderr
, "MD5 digest=");
2911 for (i
= 0; i
< maxlen
; ++i
) {
2912 fprintf (stderr
, "%02x", ct
->c_digest
[i
] & 0xff);
2914 fprintf (stderr
, "\n");
2920 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2921 (int) strlen ((char *) digest
));
2931 /* Multipart parts might have content before the first subpart and/or
2932 after the last subpart that hasn't been stored anywhere else, so do
2935 get_leftover_mp_content (CT ct
, int before
/* or after */)
2937 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2939 int found_boundary
= 0;
2945 char *content
= NULL
;
2947 if (! m
) return NOTOK
;
2950 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2952 /* Isolate the beginning of this part to the beginning of the
2953 first subpart and save any content between them. */
2954 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2955 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2956 boundary
= concat ("--", m
->mp_start
, NULL
);
2958 struct part
*last_subpart
= NULL
;
2959 struct part
*subpart
;
2961 /* Go to the last subpart to get its end position. */
2962 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2963 last_subpart
= subpart
;
2966 if (last_subpart
== NULL
) return NOTOK
;
2968 /* Isolate the end of the last subpart to the end of this part
2969 and save any content between them. */
2970 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2971 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2972 boundary
= concat ("--", m
->mp_stop
, NULL
);
2975 /* Back up by 1 to pick up the newline. */
2976 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2978 /* Don't look beyond beginning of first subpart (before) or
2979 next part (after). */
2980 if (read
> max
) bufp
[read
-max
] = '\0';
2983 if (! strcmp (bufp
, boundary
)) {
2987 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2993 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2995 char *old_content
= content
;
2996 content
= concat (content
, bufp
, NULL
);
3000 ? concat ("\n", bufp
, NULL
)
3001 : concat (bufp
, NULL
);
3006 if (found_boundary
|| read
> max
) break;
3008 if (read
> max
) break;
3012 /* Skip the newline if that's all there is. */
3016 /* Remove trailing newline, except at EOF. */
3017 if ((before
|| ! feof (ct
->c_fp
)) &&
3018 (cp
= content
+ strlen (content
)) > content
&&
3023 if (strlen (content
) > 1) {
3025 m
->mp_content_before
= content
;
3027 m
->mp_content_after
= content
;
3042 ct_type_str (int type
) {
3044 case CT_APPLICATION
:
3045 return "application";
3061 return "unknown_type";
3067 ct_subtype_str (int type
, int subtype
) {
3069 case CT_APPLICATION
:
3071 case APPLICATION_OCTETS
:
3073 case APPLICATION_POSTSCRIPT
:
3074 return "postscript";
3076 return "unknown_app_subtype";
3080 case MESSAGE_RFC822
:
3082 case MESSAGE_PARTIAL
:
3084 case MESSAGE_EXTERNAL
:
3087 return "unknown_msg_subtype";
3093 case MULTI_ALTERNATE
:
3094 return "alternative";
3097 case MULTI_PARALLEL
:
3102 return "unknown_multipart_subtype";
3113 return "unknown_text_subtype";
3116 return "unknown_type";
3122 ct_str_type (const char *type
) {
3123 struct str2init
*s2i
;
3125 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3126 if (! strcasecmp (type
, s2i
->si_key
)) {
3130 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3139 ct_str_subtype (int type
, const char *subtype
) {
3143 case CT_APPLICATION
:
3144 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3145 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3149 return kv
->kv_value
;
3151 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3152 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3156 return kv
->kv_value
;
3158 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3159 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3163 return kv
->kv_value
;
3165 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3166 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3170 return kv
->kv_value
;
3177 /* Find the content type and InitFunc for the CT. */
3178 const struct str2init
*
3179 get_ct_init (int type
) {
3180 const struct str2init
*sp
;
3182 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3183 if (type
== sp
->si_val
) {
3192 ce_str (int encoding
) {
3197 return "quoted-printable";
3213 /* Find the content type and InitFunc for the content encoding method. */
3214 const struct str2init
*
3215 get_ce_method (const char *method
) {
3216 struct str2init
*sp
;
3218 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3219 if (! strcasecmp (method
, sp
->si_key
)) {
3228 * Parse a series of MIME attributes (or parameters) given a header as
3231 * Arguments include:
3233 * filename - Name of input file (for error messages)
3234 * fieldname - Name of field being processed
3235 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3236 * Updated to point to end of attributes when finished.
3237 * param_head - Pointer to head of parameter list
3238 * param_tail - Pointer to tail of parameter list
3239 * commentp - Pointer to header comment pointer (may be NULL)
3241 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3242 * DONE to indicate a benign error (minor parsing error, but the program
3247 parse_header_attrs (const char *filename
, const char *fieldname
,
3248 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3251 char *cp
= *header_attrp
;
3257 struct sectlist
*next
;
3263 struct sectlist
*sechead
;
3264 struct parmlist
*next
;
3265 } *pp
, *pp2
, *phead
= NULL
;
3267 while (*cp
== ';') {
3268 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3269 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3272 while (isspace ((unsigned char) *cp
))
3276 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3281 if (! suppress_extraneous_trailing_semicolon_warning
) {
3282 inform("extraneous trailing ';' in message %s's %s: "
3283 "parameter list", filename
, fieldname
);
3288 /* down case the attribute name */
3289 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3290 *dp
= tolower ((unsigned char) *dp
);
3292 for (up
= dp
; isspace ((unsigned char) *dp
);)
3294 if (dp
== cp
|| *dp
!= '=') {
3295 inform("invalid parameter in message %s's %s: "
3296 "field\n%*sparameter %s (error detected at offset %d)",
3297 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3302 * To handle RFC 2231, we have to deal with the following extensions:
3304 * name*=encoded-value
3305 * name*<N>=part-N-of-a-parameter-value
3306 * name*<N>*=encoded-part-N-of-a-parameter-value
3309 * If there's a * right before the equal sign, it's encoded.
3310 * If there's a * and one or more digits, then it's section N.
3312 * Remember we can have one or the other, or both. cp points to
3313 * beginning of name, up points past the last character in the
3317 for (vp
= cp
; vp
< up
; vp
++) {
3318 if (*vp
== '*' && vp
< up
- 1) {
3321 } else if (*vp
== '*' && vp
== up
- 1) {
3323 } else if (partial
) {
3324 if (isdigit((unsigned char) *vp
))
3325 index
= *vp
- '0' + index
* 10;
3327 inform("invalid parameter index in message %s's "
3328 "%s: field\n%*s(parameter %s)", filename
,
3329 fieldname
, strlen(invo_name
) + 2, "", cp
);
3338 * Break out the parameter name and value sections and allocate
3342 nameptr
= mh_xmalloc(len
+ 1);
3343 strncpy(nameptr
, cp
, len
);
3344 nameptr
[len
] = '\0';
3346 for (dp
++; isspace ((unsigned char) *dp
);)
3351 * Single quotes delimit the character set and language tag.
3352 * They are required on the first section (or a complete
3357 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3363 charset
= mh_xmalloc(len
+ 1);
3364 strncpy(charset
, dp
, len
);
3365 charset
[len
] = '\0';
3371 inform("missing charset in message %s's %s: "
3372 "field\n%*s(parameter %s)", filename
, fieldname
,
3373 strlen(invo_name
) + 2, "", nameptr
);
3379 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3386 lang
= mh_xmalloc(len
+ 1);
3387 strncpy(lang
, dp
, len
);
3394 inform("missing language tag in message %s's %s: "
3395 "field\n%*s(parameter %s)", filename
, fieldname
,
3396 strlen(invo_name
) + 2, "", nameptr
);
3406 * At this point vp should be pointing at the beginning
3407 * of the encoded value/section. Continue until we reach
3408 * the end or get whitespace. But first, calculate the
3409 * length so we can allocate the correct buffer size.
3412 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3414 if (*(vp
+ 1) == '\0' ||
3415 !isxdigit((unsigned char) *(vp
+ 1)) ||
3416 *(vp
+ 2) == '\0' ||
3417 !isxdigit((unsigned char) *(vp
+ 2))) {
3418 inform("invalid encoded sequence in message "
3419 "%s's %s: field\n%*s(parameter %s)",
3420 filename
, fieldname
, strlen(invo_name
) + 2,
3432 up
= valptr
= mh_xmalloc(len
+ 1);
3434 for (vp
= dp
; istoken(*vp
); vp
++) {
3436 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3447 * A "normal" string. If it's got a leading quote, then we
3448 * strip the quotes out. Otherwise go until we reach the end
3449 * or get whitespace. Note we scan it twice; once to get the
3450 * length, then the second time copies it into the destination
3457 for (cp
= dp
+ 1;;) {
3461 inform("invalid quoted-string in message %s's %s: "
3462 "field\n%*s(parameter %s)", filename
,
3463 fieldname
, strlen(invo_name
) + 2, "", nameptr
);
3483 for (cp
= dp
; istoken (*cp
); cp
++) {
3488 valptr
= mh_xmalloc(len
+ 1);
3492 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3500 strncpy(valptr
, cp
= dp
, len
);
3508 * If 'partial' is set, we don't allocate a parameter now. We
3509 * put it on the parameter linked list to be reassembled later.
3511 * "phead" points to a list of all parameters we need to reassemble.
3512 * Each parameter has a list of sections. We insert the sections in
3517 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3518 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3533 * Insert this into the section linked list
3541 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3542 sp
->next
= pp
->sechead
;
3545 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3546 if (sp2
->index
== sp
->index
) {
3547 inform("duplicate index (%d) in message "
3548 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3549 filename
, fieldname
, strlen(invo_name
) + 2, "",
3553 if (sp2
->index
< sp
->index
&&
3554 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3555 sp
->next
= sp2
->next
;
3562 inform("Internal error: cannot insert partial "
3563 "param in message %s's %s: field\n%*s(parameter %s)",
3564 filename
, fieldname
, strlen(invo_name
) + 2, "",
3571 * Save our charset and lang tags.
3574 if (index
== 0 && encoded
) {
3575 mh_xfree(pp
->charset
);
3576 pp
->charset
= charset
;
3581 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3582 pm
->pm_charset
= charset
;
3586 while (isspace ((unsigned char) *cp
))
3590 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3596 * Now that we're done, reassemble all of the partial parameters.
3599 for (pp
= phead
; pp
!= NULL
; ) {
3603 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3604 if (sp
->index
!= pindex
++) {
3605 inform("missing section %d for parameter in "
3606 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3607 filename
, fieldname
, strlen(invo_name
) + 2, "",
3614 p
= q
= mh_xmalloc(tlen
+ 1);
3615 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3616 memcpy(q
, sp
->value
, sp
->len
);
3626 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3627 pm
->pm_charset
= pp
->charset
;
3628 pm
->pm_lang
= pp
->lang
;
3639 * Return the charset for a particular content type.
3643 content_charset (CT ct
) {
3644 char *ret_charset
= NULL
;
3646 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3648 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3653 * Create a string based on a list of output parameters. Assume that this
3654 * parameter string will be appended to an existing header, so start out
3655 * with the separator (;). Perform RFC 2231 encoding when necessary.
3659 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3661 char *paramout
= NULL
;
3662 char line
[CPERLIN
* 2], *q
;
3663 int curlen
, index
, cont
, encode
, i
;
3664 size_t valoff
, numchars
;
3666 while (params
!= NULL
) {
3672 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3675 if (strlen(params
->pm_name
) > CPERLIN
) {
3676 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3681 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3684 * Loop until we get a parameter that fits within a line. We
3685 * assume new lines start with a tab, so check our overflow based
3695 * At this point we're definitely continuing the line, so
3696 * be sure to include the parameter name and section index.
3699 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3700 params
->pm_name
, index
);
3703 * Both of these functions do a NUL termination
3707 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3708 numchars
, valoff
, index
);
3710 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3720 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3725 * "line" starts with a ;\n\t, so that doesn't count against
3726 * the length. But add 8 since it starts with a tab; that's
3727 * how we end up with 5.
3730 initialwidth
= strlen(line
) + 5;
3733 * At this point the line should be built, so add it to our
3734 * current output buffer.
3737 paramout
= add(line
, paramout
);
3741 * If this won't fit on the line, start a new one. Save room in
3742 * case we need a semicolon on the end
3745 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3757 * At this point, we're either finishing a contined parameter, or
3758 * we're working on a new one.
3762 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3763 params
->pm_name
, index
);
3765 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3770 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3771 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3773 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3774 strlen(params
->pm_value
+ valoff
), valoff
);
3781 paramout
= add(line
, paramout
);
3782 initialwidth
+= strlen(line
);
3784 params
= params
->pm_next
;
3788 *offsetout
= initialwidth
;
3794 * Calculate the size of a parameter.
3798 * pm - The parameter being output
3799 * index - If continuing the parameter, the index of the section
3801 * valueoff - The current offset into the parameter value that we're
3802 * working on (previous sections have consumed valueoff bytes).
3803 * encode - Set if we should perform encoding on this parameter section
3804 * (given that we're consuming bytesfit bytes).
3805 * cont - Set if the remaining data in value will not fit on a single
3806 * line and will need to be continued.
3807 * bytesfit - The number of bytes that we can consume from the parameter
3808 * value and still fit on a completely new line. The
3809 * calculation assumes the new line starts with a tab,
3810 * includes the parameter name and any encoding, and fits
3811 * within CPERLIN bytes. Will always be at least 1.
3815 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3818 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3819 size_t len
= 0, fit
= 0;
3820 int fitlimit
= 0, eightbit
, maxfit
;
3825 * Add up the length. First, start with the parameter name.
3828 len
= strlen(pm
->pm_name
);
3831 * Scan the parameter value and see if we need to do encoding for this
3835 eightbit
= contains8bit(start
, NULL
);
3838 * Determine if we need to encode this section. Encoding is necessary if:
3840 * - There are any 8-bit characters at all and we're on the first
3842 * - There are 8-bit characters within N bytes of our section start.
3843 * N is calculated based on the number of bytes it would take to
3844 * reach CPERLIN. Specifically:
3845 * 8 (starting tab) +
3846 * strlen(param name) +
3847 * 4 ('* for section marker, '=', opening/closing '"')
3849 * is the number of bytes used by everything that isn't part of the
3850 * value. So that gets subtracted from CPERLIN.
3853 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3854 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3855 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3859 len
++; /* Add in equal sign */
3863 * We're using maxfit as a marker for how many characters we can
3864 * fit into the line. Bump it by two because we're not using quotes
3871 * If we don't have a charset or language tag in this parameter,
3875 if (! pm
->pm_charset
) {
3876 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3877 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3878 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3879 "local character set is US-ASCII", pm
->pm_name
);
3882 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3884 len
++; /* For the encoding marker */
3887 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3892 * We know we definitely need to include an index. maxfit already
3893 * includes the section marker.
3895 len
+= strlen(indexchar
);
3897 for (p
= start
; *p
!= '\0'; p
++) {
3898 if (isparamencode(*p
)) {
3906 * Just so there's no confusion: maxfit is counting OUTPUT
3907 * characters (post-encoding). fit is counting INPUT characters.
3909 if (! fitlimit
&& maxfit
>= 0)
3911 else if (! fitlimit
)
3916 * Calculate the string length, but add room for quoting \
3917 * and " if necessary. Also account for quotes at beginning
3920 for (p
= start
; *p
!= '\0'; p
++) {
3931 if (! fitlimit
&& maxfit
>= 0)
3933 else if (! fitlimit
)
3950 * Output an encoded parameter string.
3954 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3955 size_t valueoff
, int index
)
3957 size_t outlen
= 0, n
;
3958 char *endptr
= output
+ len
, *p
;
3961 * First, output the marker for an encoded string.
3969 * If the index is 0, output the character set and language tag.
3970 * If theses were NULL, they should have already been filled in
3975 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3979 if (output
> endptr
) {
3980 inform("Internal error: parameter buffer overflow");
3986 * Copy over the value, encoding if necessary
3989 p
= pm
->pm_value
+ valueoff
;
3990 while (valuelen
-- > 0) {
3991 if (isparamencode(*p
)) {
3992 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
3999 if (output
> endptr
) {
4000 inform("Internal error: parameter buffer overflow");
4011 * Output a "normal" parameter, without encoding. Be sure to escape
4012 * quotes and backslashes if necessary.
4016 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4020 char *endptr
= output
+ len
, *p
;
4026 p
= pm
->pm_value
+ valueoff
;
4028 while (valuelen
-- > 0) {
4039 if (output
> endptr
) {
4040 inform("Internal error: parameter buffer overflow");
4045 if (output
- 2 > endptr
) {
4046 inform("Internal error: parameter buffer overflow");
4057 * Add a parameter to the parameter linked list
4061 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4066 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4067 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4070 (*last
)->pm_next
= pm
;
4081 * Either replace a current parameter with a new value, or add the parameter
4082 * to the parameter linked list.
4086 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4090 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4091 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4093 * If nocopy is set, it's assumed that we own both name
4094 * and value. We don't need name, so we discard it now.
4099 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4104 return add_param(first
, last
, name
, value
, nocopy
);
4108 * Retrieve a parameter value from a parameter linked list. If the parameter
4109 * value needs converted to the local character set, do that now.
4113 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4115 while (first
!= NULL
) {
4116 if (strcasecmp(name
, first
->pm_name
) == 0) {
4118 return first
->pm_value
;
4119 return getcpy(get_param_value(first
, replace
));
4121 first
= first
->pm_next
;
4128 * Return a parameter value, converting to the local character set if
4132 char *get_param_value(PM pm
, char replace
)
4134 static char buffer
[4096]; /* I hope no parameters are larger */
4135 size_t bufsize
= sizeof(buffer
);
4140 ICONV_CONST
char *p
;
4141 #else /* HAVE_ICONV */
4143 #endif /* HAVE_ICONV */
4148 * If we don't have a character set indicated, it's assumed to be
4149 * US-ASCII. If it matches our character set, we don't need to convert
4153 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4154 strlen(pm
->pm_charset
))) {
4155 return pm
->pm_value
;
4159 * In this case, we need to convert. If we have iconv support, use
4160 * that. Otherwise, go through and simply replace every non-ASCII
4161 * character with the substitution character.
4166 bufsize
= sizeof(buffer
);
4167 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4169 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4170 if (cd
== (iconv_t
) -1) {
4174 inbytes
= strlen(pm
->pm_value
);
4178 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4179 if (errno
!= EILSEQ
) {
4184 * Reset shift state, substitute our character,
4185 * try to restart conversion.
4188 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4201 for (++p
, --inbytes
;
4202 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4221 #endif /* HAVE_ICONV */
4224 * Take everything non-ASCII and substitute the replacement character
4228 bufsize
= sizeof(buffer
);
4229 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4230 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))