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.
9 #include "sbr/m_gmprot.h"
10 #include "sbr/m_getfld.h"
11 #include "sbr/read_yes_or_no_if_tty.h"
12 #include "sbr/concat.h"
13 #include "sbr/r1bindex.h"
14 #include "sbr/ruserpass.h"
15 #include "sbr/fmt_rfc2047.h"
17 #include "sbr/check_charset.h"
18 #include "sbr/getcpy.h"
19 #include "sbr/context_find.h"
20 #include "sbr/pidstatus.h"
21 #include "sbr/arglist.h"
22 #include "sbr/error.h"
27 #include "h/mhparse.h"
30 #include "sbr/m_mktemp.h"
34 #endif /* HAVE_ICONV */
35 #include "sbr/base64.h"
41 * These are for mhfixmsg to:
42 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
44 * 2) Suppress the warning about bogus multipart content, and report it.
45 * 3) Suppress the warning about extraneous trailing ';' in header parameter
48 bool skip_mp_cte_check
;
49 bool suppress_bogus_mp_content_warning
;
50 bool bogus_mp_content
;
51 bool suppress_extraneous_trailing_semicolon_warning
;
54 * By default, suppress warning about multiple MIME-Version header fields.
56 bool suppress_multiple_mime_version_warning
= true;
58 /* list of preferred type/subtype pairs, for -prefer */
59 mime_type_subtype mime_preference
[NPREFS
];
64 * Structures for TEXT messages
66 struct k2v SubText
[] = {
67 { "plain", TEXT_PLAIN
},
68 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
69 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
70 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
73 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
76 * Structures for MULTIPART messages
78 struct k2v SubMultiPart
[] = {
79 { "mixed", MULTI_MIXED
},
80 { "alternative", MULTI_ALTERNATE
},
81 { "digest", MULTI_DIGEST
},
82 { "parallel", MULTI_PARALLEL
},
83 { "related", MULTI_RELATED
},
84 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
88 * Structures for MESSAGE messages
90 struct k2v SubMessage
[] = {
91 { "rfc822", MESSAGE_RFC822
},
92 { "external-body", MESSAGE_EXTERNAL
},
93 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
97 * Structure for APPLICATION messages
99 struct k2v SubApplication
[] = {
100 { "octet-stream", APPLICATION_OCTETS
},
101 { "postscript", APPLICATION_POSTSCRIPT
},
102 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
106 * Mapping of names of CTE types in mhbuild directives
108 static struct k2v EncodingType
[] = {
111 { "q-p", CE_QUOTED
},
112 { "quoted-printable", CE_QUOTED
},
113 { "b64", CE_BASE64
},
114 { "base64", CE_BASE64
},
122 static CT
get_content (FILE *, char *, int);
123 static int get_comment (const char *, const char *, char **, char **);
125 static int InitGeneric (CT
);
126 static int InitText (CT
);
127 static int InitMultiPart (CT
);
128 static void reverse_parts (CT
);
129 static void prefer_parts(CT ct
);
130 static int InitMessage (CT
);
131 static int InitApplication (CT
);
132 static int init_encoding (CT
, OpenCEFunc
);
133 static unsigned long size_encoding (CT
);
134 static int InitBase64 (CT
);
135 static int openBase64 (CT
, char **);
136 static int InitQuoted (CT
);
137 static int openQuoted (CT
, char **);
138 static int Init7Bit (CT
);
139 static int openExternal (CT
, CE
, char **, int *);
140 static int InitFile (CT
);
141 static int openFile (CT
, char **);
142 static int InitFTP (CT
);
143 static int openFTP (CT
, char **);
144 static int InitMail (CT
);
145 static int openMail (CT
, char **);
146 static int get_leftover_mp_content (CT
, int);
147 static int InitURL (CT
);
148 static int openURL (CT
, char **);
149 static int parse_header_attrs (const char *, const char *, char **, PM
*,
151 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
152 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
153 static int get_dispo (char *, CT
, int);
155 struct str2init str2cts
[] = {
156 { "application", CT_APPLICATION
, InitApplication
},
157 { "audio", CT_AUDIO
, InitGeneric
},
158 { "image", CT_IMAGE
, InitGeneric
},
159 { "message", CT_MESSAGE
, InitMessage
},
160 { "multipart", CT_MULTIPART
, InitMultiPart
},
161 { "text", CT_TEXT
, InitText
},
162 { "video", CT_VIDEO
, InitGeneric
},
163 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
164 { NULL
, CT_UNKNOWN
, NULL
},
167 struct str2init str2ces
[] = {
168 { "base64", CE_BASE64
, InitBase64
},
169 { "quoted-printable", CE_QUOTED
, InitQuoted
},
170 { "8bit", CE_8BIT
, Init7Bit
},
171 { "7bit", CE_7BIT
, Init7Bit
},
172 { "binary", CE_BINARY
, Init7Bit
},
173 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
174 { NULL
, CE_UNKNOWN
, NULL
},
178 * NOTE WELL: si_key MUST NOT have value of NOTOK
180 * si_val is 1 if access method is anonymous.
182 struct str2init str2methods
[] = {
183 { "afs", 1, InitFile
},
184 { "anon-ftp", 1, InitFTP
},
185 { "ftp", 0, InitFTP
},
186 { "local-file", 0, InitFile
},
187 { "mail-server", 0, InitMail
},
188 { "url", 0, InitURL
},
194 * Main entry point for parsing a MIME message or file.
195 * It returns the Content structure for the top level
196 * entity in the file.
200 parse_mime (char *file
)
209 bogus_mp_content
= false;
212 * Check if file is actually standard input
214 if ((is_stdin
= !(strcmp (file
, "-")))) {
215 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
217 advise("mhparse", "unable to create temporary file in %s",
221 file
= mh_xstrdup(tfile
);
223 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
224 if (fwrite(buffer
, 1, n
, fp
) != n
) {
225 (void) m_unlink (file
);
226 advise (file
, "error copying to temporary file");
232 if (ferror (stdin
)) {
233 (void) m_unlink (file
);
234 advise ("stdin", "error reading");
238 (void) m_unlink (file
);
239 advise (file
, "error writing");
242 fseek (fp
, 0L, SEEK_SET
);
243 } else if (stat (file
, &statbuf
) == NOTOK
) {
244 advise (file
, "unable to stat");
246 } else if (S_ISDIR(statbuf
.st_mode
)) {
247 /* Don't try to parse a directory. */
248 inform("%s is a directory", file
);
250 } else if ((fp
= fopen (file
, "r")) == NULL
) {
251 advise (file
, "unable to read");
255 if (!(ct
= get_content (fp
, file
, 1))) {
257 (void) m_unlink (file
);
258 inform("unable to decode %s", file
);
263 ct
->c_unlink
= 1; /* temp file to remove */
267 if (ct
->c_end
== 0L) {
268 fseek (fp
, 0L, SEEK_END
);
269 ct
->c_end
= ftell (fp
);
272 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
284 * Main routine for reading/parsing the headers
285 * of a message content.
287 * toplevel = 1 # we are at the top level of the message
288 * toplevel = 0 # we are inside message type or multipart type
289 * # other than multipart/digest
290 * toplevel = -1 # we are inside multipart/digest
291 * NB: on failure we will fclose(in)!
295 get_content (FILE *in
, char *file
, int toplevel
)
298 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
302 m_getfld_state_t gstate
;
304 /* allocate the content structure */
307 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
308 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
311 * Parse the header fields for this
312 * content into a linked list.
314 gstate
= m_getfld_state_init(in
);
315 m_getfld_track_filepos2(&gstate
);
316 for (compnum
= 1;;) {
317 int bufsz
= sizeof buf
;
318 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
323 /* get copies of the buffers */
324 np
= mh_xstrdup(name
);
325 vp
= mh_xstrdup(buf
);
327 /* if necessary, get rest of field */
328 while (state
== FLDPLUS
) {
330 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
331 vp
= add (buf
, vp
); /* add to previous value */
334 /* Now add the header data to the list */
335 add_header (ct
, np
, vp
);
337 /* continue, to see if this isn't the last header field */
338 ct
->c_begin
= ftell (in
) + 1;
342 /* There are two cases. The unusual one is when there is no
343 * blank line between the headers and the body. This is
344 * indicated by the name of the header starting with `:'.
346 * For both cases, normal first, `1' is the desired c_begin
347 * file position for the start of the body, and `2' is the
348 * file position when buf is returned.
350 * f o o : b a r \n \n b o d y \n bufsz = 6
352 * f o o : b a r \n b o d y \n bufsz = 4
355 * For the normal case, bufsz includes the
356 * header-terminating `\n', even though it is not in buf,
357 * but bufsz isn't affected when it's missing in the unusual
359 if (name
[0] == ':') {
360 ct
->c_begin
= ftell(in
) - bufsz
;
362 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
367 ct
->c_begin
= ftell (in
);
372 die("message format error in component #%d", compnum
);
375 die("getfld() returned %d", state
);
378 /* break out of the loop */
381 m_getfld_state_destroy (&gstate
);
384 * Read the content headers. We will parse the
385 * MIME related header fields into their various
386 * structures and set internal flags related to
387 * content type/subtype, etc.
390 hp
= ct
->c_first_hf
; /* start at first header field */
392 /* Get MIME-Version field */
393 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
398 vrsn
= mh_xstrdup(FENDNULL(hp
->value
));
400 /* Now, cleanup this field */
403 while (isspace ((unsigned char) *cp
))
405 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
407 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
408 if (!isspace ((unsigned char) *dp
))
412 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
415 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
418 for (dp
= cp
; istoken (*dp
); dp
++)
422 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
425 inform("message %s has unknown value for %s: field (%s), continuing...",
426 ct
->c_file
, VRSN_FIELD
, cp
);
431 if (! suppress_multiple_mime_version_warning
)
432 inform("message %s has multiple %s: fields",
433 ct
->c_file
, VRSN_FIELD
);
437 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
438 /* Get Content-Type field */
439 struct str2init
*s2i
;
440 CI ci
= &ct
->c_ctinfo
;
442 /* Check if we've already seen a Content-Type header */
444 inform("message %s has multiple %s: fields",
445 ct
->c_file
, TYPE_FIELD
);
449 /* Parse the Content-Type field */
450 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
454 * Set the Init function and the internal
455 * flag for this content type.
457 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
458 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
460 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
462 ct
->c_type
= s2i
->si_val
;
463 ct
->c_ctinitfnx
= s2i
->si_init
;
465 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
466 /* Get Content-Transfer-Encoding field */
468 struct str2init
*s2i
;
471 * Check if we've already seen the
472 * Content-Transfer-Encoding field
475 inform("message %s has multiple %s: fields",
476 ct
->c_file
, ENCODING_FIELD
);
480 /* get copy of this field */
481 ct
->c_celine
= cp
= mh_xstrdup(FENDNULL(hp
->value
));
483 while (isspace ((unsigned char) *cp
))
485 for (dp
= cp
; istoken (*dp
); dp
++)
491 * Find the internal flag and Init function
492 * for this transfer encoding.
494 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
495 if (!strcasecmp (cp
, s2i
->si_key
))
497 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
500 ct
->c_encoding
= s2i
->si_val
;
502 /* Call the Init function for this encoding */
503 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
506 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
507 /* Get Content-ID field */
508 ct
->c_id
= add (hp
->value
, ct
->c_id
);
510 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
511 /* Get Content-Description field */
512 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
514 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
515 /* Get Content-Disposition field */
516 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
521 hp
= hp
->next
; /* next header field */
525 * Check if we saw a Content-Type field.
526 * If not, then assign a default value for
527 * it, and the Init function.
531 * If we are inside a multipart/digest message,
532 * so default type is message/rfc822
535 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
537 ct
->c_type
= CT_MESSAGE
;
538 ct
->c_ctinitfnx
= InitMessage
;
541 * Else default type is text/plain
543 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
545 ct
->c_type
= CT_TEXT
;
546 ct
->c_ctinitfnx
= InitText
;
550 /* Use default Transfer-Encoding, if necessary */
552 ct
->c_encoding
= CE_7BIT
;
565 * small routine to add header field to list
569 add_header (CT ct
, char *name
, char *value
)
573 /* allocate header field structure */
576 /* link data into header structure */
581 /* link header structure into the list */
582 if (ct
->c_first_hf
== NULL
) {
583 ct
->c_first_hf
= hp
; /* this is the first */
586 ct
->c_last_hf
->next
= hp
; /* add it to the end */
595 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
596 * directives. Fills in the information of the CTinfo structure.
599 get_ctinfo (char *cp
, CT ct
, int magic
)
608 /* store copy of Content-Type line */
609 cp
= ct
->c_ctline
= mh_xstrdup(FENDNULL(cp
));
611 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
614 /* change newlines to spaces */
615 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
618 /* trim trailing spaces */
619 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
620 if (!isspace ((unsigned char) *dp
))
625 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
627 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
628 &ci
->ci_comment
) == NOTOK
)
631 for (dp
= cp
; istoken (*dp
); dp
++)
635 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
640 inform("invalid %s: field in message %s (empty type)",
641 TYPE_FIELD
, ct
->c_file
);
644 to_lower(ci
->ci_type
);
646 while (isspace ((unsigned char) *cp
))
649 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
650 &ci
->ci_comment
) == NOTOK
)
655 ci
->ci_subtype
= mh_xstrdup("");
660 while (isspace ((unsigned char) *cp
))
663 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
664 &ci
->ci_comment
) == NOTOK
)
667 for (dp
= cp
; istoken (*dp
); dp
++)
671 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
675 if (!*ci
->ci_subtype
) {
676 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
677 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
680 to_lower(ci
->ci_subtype
);
683 while (isspace ((unsigned char) *cp
))
686 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
687 &ci
->ci_comment
) == NOTOK
)
690 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
691 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
692 &ci
->ci_comment
)) != OK
) {
693 return status
== NOTOK
? NOTOK
: OK
;
697 * Get any <Content-Id> given in buffer
699 if (magic
&& *cp
== '<') {
702 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
703 inform("invalid ID in message %s", ct
->c_file
);
709 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
715 while (isspace ((unsigned char) *cp
))
720 * Get any [Content-Description] given in buffer.
722 if (magic
&& *cp
== '[') {
724 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
728 inform("invalid description in message %s", ct
->c_file
);
736 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
742 while (isspace ((unsigned char) *cp
))
747 * Get any {Content-Disposition} given in buffer.
749 if (magic
&& *cp
== '{') {
751 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
755 inform("invalid disposition in message %s", ct
->c_file
);
763 if (get_dispo(cp
, ct
, 1) != OK
)
769 while (isspace ((unsigned char) *cp
))
774 * Get any extension directives (right now just the content transfer
775 * encoding, but maybe others) that we care about.
778 if (magic
&& *cp
== '*') {
780 * See if it's a CTE we match on
785 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
789 inform("invalid null transfer encoding specification");
796 ct
->c_reqencoding
= CE_UNKNOWN
;
798 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
799 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
800 ct
->c_reqencoding
= kv
->kv_value
;
805 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
806 inform("invalid CTE specification: \"%s\"", dp
);
810 while (isspace ((unsigned char) *cp
))
815 * Check if anything is left over
819 ci
->ci_magic
= mh_xstrdup(cp
);
821 /* If there is a Content-Disposition header and it doesn't
822 have a *filename=, extract it from the magic contents.
823 The r1bindex call skips any leading directory
825 if (ct
->c_dispo_type
&&
826 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
827 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
828 r1bindex(ci
->ci_magic
, '/'), 0);
832 inform("extraneous information in message %s's %s: field\n"
833 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
841 * Parse out a Content-Disposition header. A lot of this is cribbed from
845 get_dispo (char *cp
, CT ct
, int buildflag
)
847 char *dp
, *dispoheader
;
852 * Save the whole copy of the Content-Disposition header, unless we're
853 * processing a mhbuild directive. A NULL c_dispo will be a flag to
854 * mhbuild that the disposition header needs to be generated at that
858 dispoheader
= cp
= mh_xstrdup(FENDNULL(cp
));
860 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
863 /* change newlines to spaces */
864 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
867 /* trim trailing spaces */
868 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
869 if (!isspace ((unsigned char) *dp
))
874 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
876 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
882 for (dp
= cp
; istoken (*dp
); dp
++)
886 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
890 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
893 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
894 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
896 if (status
== NOTOK
) {
901 inform("extraneous information in message %s's %s: field\n (%s)",
902 ct
->c_file
, DISPO_FIELD
, cp
);
908 ct
->c_dispo
= dispoheader
;
915 get_comment (const char *filename
, const char *fieldname
, char **ap
,
920 char c
, buffer
[BUFSIZ
], *dp
;
930 inform("invalid comment in message %s's %s: field",
931 filename
, fieldname
);
936 if ((c
= *cp
++) == '\0')
959 if ((dp
= *commentp
)) {
960 *commentp
= concat (dp
, " ", buffer
, NULL
);
963 *commentp
= mh_xstrdup(buffer
);
967 while (isspace ((unsigned char) *cp
))
978 * Handles content types audio, image, and video.
979 * There's not much to do right here.
987 return OK
; /* not much to do here */
1003 CI ci
= &ct
->c_ctinfo
;
1005 /* check for missing subtype */
1006 if (!*ci
->ci_subtype
)
1007 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1010 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1012 /* allocate text character set structure */
1014 ct
->c_ctparams
= (void *) t
;
1016 /* scan for charset parameter */
1017 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1018 if (!strcasecmp (pm
->pm_name
, "charset"))
1021 /* check if content specified a character set */
1023 chset
= pm
->pm_value
;
1024 t
->tx_charset
= CHARSET_SPECIFIED
;
1026 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1030 * If we can not handle character set natively,
1031 * then check profile for string to modify the
1032 * terminal or display method.
1034 * termproc is for mhshow, though mhlist -debug prints it, too.
1036 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1037 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1038 if ((cp
= context_find (buffer
)))
1039 ct
->c_termproc
= mh_xstrdup(cp
);
1051 InitMultiPart (CT ct
)
1061 struct multipart
*m
;
1062 struct part
*part
, **next
;
1063 CI ci
= &ct
->c_ctinfo
;
1068 * The encoding for multipart messages must be either
1069 * 7bit, 8bit, or binary (per RFC 2045).
1071 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1072 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1073 /* Copy the Content-Transfer-Encoding header field body so we can
1074 remove any trailing whitespace and leading blanks from it. */
1075 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1077 bp
= cte
+ strlen (cte
) - 1;
1078 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1079 for (bp
= cte
; isblank((unsigned char)*bp
); ++bp
) continue;
1081 inform("\"%s/%s\" type in message %s must be encoded in\n"
1082 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1083 "mhfixmsg -fixcte can fix it, or\n"
1084 "manually edit the file and change the \"%s\"\n"
1085 "Content-Transfer-Encoding to one of those. For now, continuing...",
1086 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1093 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1096 * Check for "boundary" parameter, which is
1097 * required for multipart messages.
1100 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1101 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1107 /* complain if boundary parameter is missing */
1109 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1110 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1114 /* allocate primary structure for multipart info */
1116 ct
->c_ctparams
= (void *) m
;
1118 /* check if boundary parameter contains only whitespace characters */
1119 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1122 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1123 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1127 /* remove trailing whitespace from boundary parameter */
1128 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1129 if (!isspace ((unsigned char) *dp
))
1133 /* record boundary separators */
1134 m
->mp_start
= concat (bp
, "\n", NULL
);
1135 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1137 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1138 advise (ct
->c_file
, "unable to open for reading");
1142 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1144 next
= &m
->mp_parts
;
1148 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1153 if (bufp
[0] != '-' || bufp
[1] != '-')
1157 * A bit of a lame hack; if this line ends in \r\n then replace
1158 * the \r\n with just a \n so that the boundary markers will match
1159 * up properly in case this uses "MS-DOS" line endings.
1162 if (gotlen
> 2 && bufp
[gotlen
- 1] == '\n' &&
1163 bufp
[gotlen
- 2] == '\r') {
1165 * Note we don't change getpos here, because it is used to
1166 * calculate multipart offsets.
1168 bufp
[gotlen
- 2] = '\n';
1169 bufp
[gotlen
- 1] = '\0';
1173 if (strcmp (bufp
+ 2, m
->mp_start
))
1178 next
= &part
->mp_next
;
1180 if (!(p
= get_content (fp
, ct
->c_file
,
1181 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1189 fseek (fp
, pos
, SEEK_SET
);
1192 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1196 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1197 if (p
->c_end
< p
->c_begin
)
1198 p
->c_begin
= p
->c_end
;
1203 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1208 if (! suppress_bogus_mp_content_warning
) {
1209 inform("bogus multipart content in message %s", ct
->c_file
);
1211 bogus_mp_content
= true;
1213 if (!inout
&& part
) {
1215 p
->c_end
= ct
->c_end
;
1217 if (p
->c_begin
>= p
->c_end
) {
1218 for (next
= &m
->mp_parts
; *next
!= part
;
1219 next
= &((*next
)->mp_next
))
1228 /* reverse the order of the parts for multipart/alternative */
1229 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1235 * label all subparts with part number, and
1236 * then initialize the content of the subpart.
1241 char partnam
[BUFSIZ
];
1244 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1245 pp
= partnam
+ strlen (partnam
);
1250 for (part
= m
->mp_parts
, partnum
= 1; part
;
1251 part
= part
->mp_next
, partnum
++) {
1254 sprintf (pp
, "%d", partnum
);
1255 p
->c_partno
= mh_xstrdup(partnam
);
1257 /* initialize the content of the subparts */
1258 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1267 get_leftover_mp_content (ct
, 1);
1268 get_leftover_mp_content (ct
, 0);
1278 * reverse the order of the parts of a multipart/alternative,
1279 * presumably to put the "most favored" alternative first, for
1280 * ease of choosing/displaying it later on. from a mail message on
1281 * nmh-workers, from kenh:
1282 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1283 * see code in mhn that did the same thing... According to the RCS
1284 * logs, that code was around from the initial checkin of mhn.c by
1285 * John Romine in 1992, which is as far back as we have."
1288 reverse_parts (CT ct
)
1290 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1294 /* Reverse the order of its parts by walking the mp_parts list
1295 and pushing each node to the front. */
1296 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1297 next
= part
->mp_next
;
1298 part
->mp_next
= m
->mp_parts
;
1304 move_preferred_part(CT ct
, mime_type_subtype
*pref
)
1306 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1307 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1311 /* move the matching part(s) to the head of the list: walk the
1312 * list of parts, move matching parts to a new list (maintaining
1313 * their order), and finally, concatenate the old list onto the
1320 head
->mp_next
= m
->mp_parts
;
1321 nhead
->mp_next
= NULL
;
1325 part
= head
->mp_next
;
1326 while (part
!= NULL
) {
1327 ci
= &part
->mp_part
->c_ctinfo
;
1328 if (!strcasecmp(ci
->ci_type
, pref
->type
) &&
1330 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1331 prev
->mp_next
= part
->mp_next
;
1332 part
->mp_next
= NULL
;
1333 ntail
->mp_next
= part
;
1335 part
= prev
->mp_next
;
1338 part
= prev
->mp_next
;
1341 ntail
->mp_next
= head
->mp_next
;
1342 m
->mp_parts
= nhead
->mp_next
;
1346 * move parts that match the user's preferences (-prefer) to the head
1347 * of the line. process preferences in reverse so first one given
1348 * ends up first in line
1354 for (i
= 0; i
< npreferred
; i
++)
1355 move_preferred_part(ct
, mime_preference
+ i
);
1360 /* parse_mime() arranges alternates in reverse (priority) order. This
1361 function can be used to reverse them back. This will put, for
1362 example, a text/plain part before a text/html part in a
1363 multipart/alternative part, for example, where it belongs. */
1365 reverse_alternative_parts (CT ct
)
1367 if (ct
->c_type
== CT_MULTIPART
) {
1368 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1371 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1375 /* And call recursively on each part of a multipart. */
1376 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1377 reverse_alternative_parts (part
->mp_part
);
1390 CI ci
= &ct
->c_ctinfo
;
1392 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1393 inform("\"%s/%s\" type in message %s should be encoded in "
1394 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1399 /* check for missing subtype */
1400 if (!*ci
->ci_subtype
)
1401 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1404 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1406 switch (ct
->c_subtype
) {
1407 case MESSAGE_RFC822
:
1410 case MESSAGE_EXTERNAL
:
1418 ct
->c_ctparams
= (void *) e
;
1421 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1422 advise (ct
->c_file
, "unable to open for reading");
1426 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1428 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1436 p
->c_ceopenfnx
= NULL
;
1437 if ((exresult
= params_external (ct
, 0)) != NOTOK
1438 && p
->c_ceopenfnx
== openMail
) {
1442 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1444 content_error (NULL
, ct
,
1445 "empty body for access-type=mail-server");
1449 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1450 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1452 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1454 adios ("failed", "fread");
1457 die("unexpected EOF from fread");
1460 bp
+= cc
, size
-= cc
;
1467 p
->c_end
= p
->c_begin
;
1472 if (exresult
== NOTOK
)
1474 if (e
->eb_flags
== NOTOK
)
1477 switch (p
->c_type
) {
1482 if (p
->c_subtype
!= MESSAGE_RFC822
)
1486 e
->eb_partno
= ct
->c_partno
;
1488 (*p
->c_ctinitfnx
) (p
);
1503 params_external (CT ct
, int composing
)
1506 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1507 CI ci
= &ct
->c_ctinfo
;
1509 ct
->c_ceopenfnx
= NULL
;
1510 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1511 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1512 struct str2init
*s2i
;
1513 CT p
= e
->eb_content
;
1515 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1516 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1519 e
->eb_access
= pm
->pm_value
;
1520 e
->eb_flags
= NOTOK
;
1521 p
->c_encoding
= CE_EXTERNAL
;
1524 e
->eb_access
= s2i
->si_key
;
1525 e
->eb_flags
= s2i
->si_val
;
1526 p
->c_encoding
= CE_EXTERNAL
;
1528 /* Call the Init function for this external type */
1529 if ((*s2i
->si_init
)(p
) == NOTOK
)
1533 if (!strcasecmp (pm
->pm_name
, "name")) {
1534 e
->eb_name
= pm
->pm_value
;
1537 if (!strcasecmp (pm
->pm_name
, "permission")) {
1538 e
->eb_permission
= pm
->pm_value
;
1541 if (!strcasecmp (pm
->pm_name
, "site")) {
1542 e
->eb_site
= pm
->pm_value
;
1545 if (!strcasecmp (pm
->pm_name
, "directory")) {
1546 e
->eb_dir
= pm
->pm_value
;
1549 if (!strcasecmp (pm
->pm_name
, "mode")) {
1550 e
->eb_mode
= pm
->pm_value
;
1553 if (!strcasecmp (pm
->pm_name
, "size")) {
1554 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1557 if (!strcasecmp (pm
->pm_name
, "server")) {
1558 e
->eb_server
= pm
->pm_value
;
1561 if (!strcasecmp (pm
->pm_name
, "subject")) {
1562 e
->eb_subject
= pm
->pm_value
;
1565 if (!strcasecmp (pm
->pm_name
, "url")) {
1567 * According to RFC 2017, we have to remove all whitespace from
1571 char *u
, *p
= pm
->pm_value
;
1572 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1574 for (; *p
!= '\0'; p
++) {
1575 if (! isspace((unsigned char) *p
))
1582 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1583 e
->eb_body
= getcpy (pm
->pm_value
);
1588 if (!e
->eb_access
) {
1589 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1590 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1603 InitApplication (CT ct
)
1605 CI ci
= &ct
->c_ctinfo
;
1608 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1615 * TRANSFER ENCODINGS
1619 init_encoding (CT ct
, OpenCEFunc openfnx
)
1621 ct
->c_ceopenfnx
= openfnx
;
1622 ct
->c_ceclosefnx
= close_encoding
;
1623 ct
->c_cesizefnx
= size_encoding
;
1630 close_encoding (CT ct
)
1632 CE ce
= &ct
->c_cefile
;
1641 static unsigned long
1642 size_encoding (CT ct
)
1647 CE ce
= &ct
->c_cefile
;
1650 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1651 return (long) st
.st_size
;
1654 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1655 return (long) st
.st_size
;
1659 if (ct
->c_encoding
== CE_EXTERNAL
)
1660 return ct
->c_end
- ct
->c_begin
;
1663 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1664 return ct
->c_end
- ct
->c_begin
;
1666 if (fstat (fd
, &st
) != NOTOK
)
1667 size
= (long) st
.st_size
;
1671 (*ct
->c_ceclosefnx
) (ct
);
1683 return init_encoding (ct
, openBase64
);
1688 openBase64 (CT ct
, char **file
)
1692 bool own_ct_fp
= false;
1693 char *cp
, *buffer
= NULL
;
1694 /* sbeck -- handle suffixes */
1696 CE ce
= &ct
->c_cefile
;
1697 unsigned char *decoded
;
1701 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1706 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1707 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1713 if (*file
== NULL
) {
1716 ce
->ce_file
= mh_xstrdup(*file
);
1720 /* sbeck@cise.ufl.edu -- handle suffixes */
1722 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1723 if (ce
->ce_unlink
) {
1724 /* Create temporary file with filename extension. */
1725 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1726 die("unable to create temporary file in %s",
1730 ce
->ce_file
= add (cp
, ce
->ce_file
);
1732 } else if (*file
== NULL
) {
1734 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1735 die("unable to create temporary file in %s",
1738 ce
->ce_file
= mh_xstrdup(tempfile
);
1741 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1742 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1746 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1747 die("internal error(1)");
1749 buffer
= mh_xmalloc (len
+ 1);
1752 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1753 content_error (ct
->c_file
, ct
, "unable to open for reading");
1759 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1762 switch (cc
= read (fd
, cp
, len
)) {
1764 content_error (ct
->c_file
, ct
, "error reading from");
1768 content_error (NULL
, ct
, "premature eof");
1779 /* decodeBase64() requires null-terminated input. */
1782 if (decodeBase64 (buffer
, &decoded
, &decoded_len
,
1783 ct
->c_type
== CT_TEXT
) != OK
)
1788 unsigned char *decoded_p
= decoded
;
1789 for (i
= 0; i
< decoded_len
; ++i
) {
1790 putc (*decoded_p
++, ce
->ce_fp
);
1793 if (ferror (ce
->ce_fp
)) {
1794 content_error (ce
->ce_file
, ct
, "error writing to");
1799 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1801 if (fflush (ce
->ce_fp
)) {
1802 content_error (ce
->ce_file
, ct
, "error writing to");
1806 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1809 *file
= ce
->ce_file
;
1815 return fileno (ce
->ce_fp
);
1822 free_encoding (ct
, 0);
1832 static char hex2nib
[0x80] = {
1833 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1834 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1835 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1836 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1837 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1838 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1839 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1840 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1841 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1842 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1843 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1844 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1845 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1846 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1847 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1848 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1855 return init_encoding (ct
, openQuoted
);
1860 openQuoted (CT ct
, char **file
)
1862 int cc
, len
, quoted
;
1863 bool own_ct_fp
= false;
1869 CE ce
= &ct
->c_cefile
;
1870 /* sbeck -- handle suffixes */
1874 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1879 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1880 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1886 if (*file
== NULL
) {
1889 ce
->ce_file
= mh_xstrdup(*file
);
1893 /* sbeck@cise.ufl.edu -- handle suffixes */
1895 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1896 if (ce
->ce_unlink
) {
1897 /* Create temporary file with filename extension. */
1898 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1899 die("unable to create temporary file in %s",
1903 ce
->ce_file
= add (cp
, ce
->ce_file
);
1905 } else if (*file
== NULL
) {
1907 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1908 die("unable to create temporary file in %s",
1911 ce
->ce_file
= mh_xstrdup(tempfile
);
1914 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1915 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1919 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1920 die("internal error(2)");
1923 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1924 content_error (ct
->c_file
, ct
, "unable to open for reading");
1932 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1934 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
1935 content_error (NULL
, ct
, "premature eof");
1939 if ((cc
= gotlen
) > len
)
1943 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
1944 if (!isspace ((unsigned char) *ep
))
1949 for (; cp
< ep
; cp
++) {
1951 /* in an escape sequence */
1953 /* at byte 1 of an escape sequence */
1954 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
1955 /* next is byte 2 */
1958 /* at byte 2 of an escape sequence */
1960 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
1961 putc (mask
, ce
->ce_fp
);
1962 if (ferror (ce
->ce_fp
)) {
1963 content_error (ce
->ce_file
, ct
, "error writing to");
1966 /* finished escape sequence; next may be literal or a new
1967 * escape sequence */
1970 /* on to next byte */
1974 /* not in an escape sequence */
1976 /* starting an escape sequence, or invalid '='? */
1977 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
1978 /* "=\n" soft line break, eat the \n */
1982 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
1983 /* We don't have 2 bytes left, so this is an invalid
1984 * escape sequence; just show the raw bytes (below). */
1985 } else if (isxdigit ((unsigned char) cp
[1]) &&
1986 isxdigit ((unsigned char) cp
[2])) {
1987 /* Next 2 bytes are hex digits, making this a valid escape
1988 * sequence; let's decode it (above). */
1992 /* One or both of the next 2 is out of range, making this
1993 * an invalid escape sequence; just show the raw bytes
1997 /* Just show the raw byte. */
1998 putc (*cp
, ce
->ce_fp
);
1999 if (ferror (ce
->ce_fp
)) {
2000 content_error (ce
->ce_file
, ct
, "error writing to");
2006 content_error (NULL
, ct
,
2007 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2011 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2013 if (fflush (ce
->ce_fp
)) {
2014 content_error (ce
->ce_file
, ct
, "error writing to");
2018 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2021 *file
= ce
->ce_file
;
2027 return fileno (ce
->ce_fp
);
2030 free_encoding (ct
, 0);
2047 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2050 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2056 open7Bit (CT ct
, char **file
)
2059 bool own_ct_fp
= false;
2060 char buffer
[BUFSIZ
];
2061 /* sbeck -- handle suffixes */
2064 CE ce
= &ct
->c_cefile
;
2067 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2072 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2073 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2079 if (*file
== NULL
) {
2082 ce
->ce_file
= mh_xstrdup(*file
);
2086 /* sbeck@cise.ufl.edu -- handle suffixes */
2088 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2089 if (ce
->ce_unlink
) {
2090 /* Create temporary file with filename extension. */
2091 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2092 die("unable to create temporary file in %s",
2096 ce
->ce_file
= add (cp
, ce
->ce_file
);
2098 } else if (*file
== NULL
) {
2100 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2101 die("unable to create temporary file in %s",
2104 ce
->ce_file
= mh_xstrdup(tempfile
);
2107 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2108 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2112 if (ct
->c_type
== CT_MULTIPART
) {
2113 CI ci
= &ct
->c_ctinfo
;
2117 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2118 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2119 + 1 + strlen (ci
->ci_subtype
);
2120 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2123 fputs (buffer
, ce
->ce_fp
);
2127 if (ci
->ci_comment
) {
2128 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2129 fputs ("\n\t", ce
->ce_fp
);
2133 putc (' ', ce
->ce_fp
);
2136 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2139 fprintf (ce
->ce_fp
, "\n");
2141 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2143 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2145 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2146 fprintf (ce
->ce_fp
, "\n");
2149 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2150 die("internal error(3)");
2153 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2154 content_error (ct
->c_file
, ct
, "unable to open for reading");
2160 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2162 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2164 content_error (ct
->c_file
, ct
, "error reading from");
2168 content_error (NULL
, ct
, "premature eof");
2176 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2177 advise ("open7Bit", "fwrite");
2179 if (ferror (ce
->ce_fp
)) {
2180 content_error (ce
->ce_file
, ct
, "error writing to");
2185 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2187 if (fflush (ce
->ce_fp
)) {
2188 content_error (ce
->ce_file
, ct
, "error writing to");
2192 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2195 *file
= ce
->ce_file
;
2200 return fileno (ce
->ce_fp
);
2203 free_encoding (ct
, 0);
2217 openExternal (CT ct
, CE ce
, char **file
, int *fd
)
2220 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2225 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2226 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2232 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2236 *file
= ce
->ce_file
;
2237 *fd
= fileno (ce
->ce_fp
);
2248 return init_encoding (ct
, openFile
);
2253 openFile (CT ct
, char **file
)
2256 struct exbody
*e
= ct
->c_ctexbody
;
2257 CE ce
= &ct
->c_cefile
;
2259 switch (openExternal (e
->eb_parent
, ce
, file
, &fd
)) {
2271 content_error (NULL
, ct
, "missing name parameter");
2275 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2278 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2279 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2283 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2284 *file
= ce
->ce_file
;
2285 return fileno (ce
->ce_fp
);
2295 return init_encoding (ct
, openFTP
);
2300 openFTP (CT ct
, char **file
)
2304 char *bp
, *ftp
, *user
, *pass
;
2305 char buffer
[BUFSIZ
];
2307 CE ce
= &ct
->c_cefile
;
2308 static char *username
= NULL
;
2309 static char *password
= NULL
;
2313 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2319 switch (openExternal (e
->eb_parent
, ce
, file
, &fd
)) {
2330 if (!e
->eb_name
|| !e
->eb_site
) {
2331 content_error (NULL
, ct
, "missing %s parameter",
2332 e
->eb_name
? "site": "name");
2336 /* Get the buffer ready to go */
2338 buflen
= sizeof(buffer
);
2341 * Construct the query message for user
2343 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2349 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2355 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2356 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2361 if (e
->eb_size
> 0) {
2362 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2367 snprintf (bp
, buflen
, "? ");
2370 * Now, check the answer
2372 if (!read_yes_or_no_if_tty (buffer
))
2377 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2381 ruserpass (e
->eb_site
, &username
, &password
, 0);
2386 ce
->ce_unlink
= (*file
== NULL
);
2389 ce
->ce_file
= mh_xstrdup(*file
);
2392 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2393 die("unable to create temporary file in %s",
2396 ce
->ce_file
= mh_xstrdup(tempfile
);
2399 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2400 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2409 vec
[vecp
++] = r1bindex (ftp
, '/');
2410 vec
[vecp
++] = e
->eb_site
;
2413 vec
[vecp
++] = e
->eb_dir
;
2414 vec
[vecp
++] = e
->eb_name
;
2415 vec
[vecp
++] = ce
->ce_file
,
2416 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2417 ? "ascii" : "binary";
2425 adios ("fork", "unable to");
2429 close (fileno (ce
->ce_fp
));
2431 fprintf (stderr
, "unable to exec ");
2437 if (pidXwait (child_id
, NULL
)) {
2438 username
= password
= NULL
;
2446 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2447 *file
= ce
->ce_file
;
2448 return fileno (ce
->ce_fp
);
2459 return init_encoding (ct
, openMail
);
2464 openMail (CT ct
, char **file
)
2466 int child_id
, fd
, vecp
;
2468 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2469 struct exbody
*e
= ct
->c_ctexbody
;
2470 CE ce
= &ct
->c_cefile
;
2472 switch (openExternal (e
->eb_parent
, ce
, file
, &fd
)) {
2483 if (!e
->eb_server
) {
2484 content_error (NULL
, ct
, "missing server parameter");
2488 /* Get buffer ready to go */
2490 buflen
= sizeof(buffer
);
2492 /* Now, construct query message */
2493 snprintf (bp
, buflen
, "Retrieve content");
2499 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2505 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2507 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2509 /* Now, check answer */
2510 if (!read_yes_or_no_if_tty (buffer
))
2514 vec
[vecp
++] = r1bindex (mailproc
, '/');
2515 vec
[vecp
++] = e
->eb_server
;
2516 vec
[vecp
++] = "-subject";
2517 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2518 vec
[vecp
++] = "-body";
2519 vec
[vecp
++] = e
->eb_body
;
2525 advise ("fork", "unable to");
2529 execvp (mailproc
, vec
);
2530 fprintf (stderr
, "unable to exec ");
2536 if (pidXwait (child_id
, NULL
) == OK
)
2537 inform("request sent");
2541 if (*file
== NULL
) {
2543 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2544 die("unable to create temporary file in %s",
2547 ce
->ce_file
= mh_xstrdup(tempfile
);
2550 ce
->ce_file
= mh_xstrdup(*file
);
2554 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2555 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2559 /* showproc is for mhshow and mhstore, though mhlist -debug
2560 * prints it, too. */
2561 free(ct
->c_showproc
);
2562 ct
->c_showproc
= mh_xstrdup("true");
2564 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2565 *file
= ce
->ce_file
;
2566 return fileno (ce
->ce_fp
);
2577 return init_encoding (ct
, openURL
);
2582 openURL (CT ct
, char **file
)
2584 struct exbody
*e
= ct
->c_ctexbody
;
2585 CE ce
= &ct
->c_cefile
;
2586 char *urlprog
, *program
;
2588 struct msgs_array args
= { 0, 0, NULL
};
2591 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2595 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2599 switch (openExternal(e
->eb_parent
, ce
, file
, &fd
)) {
2611 content_error(NULL
, ct
, "missing url parameter");
2615 ce
->ce_unlink
= (*file
== NULL
);
2618 ce
->ce_file
= mh_xstrdup(*file
);
2621 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2622 die("unable to create temporary file in %s",
2625 ce
->ce_file
= mh_xstrdup(tempfile
);
2628 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2629 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2633 switch (child_id
= fork()) {
2635 adios ("fork", "unable to");
2639 argsplit_msgarg(&args
, urlprog
, &program
);
2640 app_msgarg(&args
, e
->eb_url
);
2641 app_msgarg(&args
, NULL
);
2642 dup2(fileno(ce
->ce_fp
), 1);
2643 close(fileno(ce
->ce_fp
));
2644 execvp(program
, args
.msgs
);
2645 fprintf(stderr
, "Unable to exec ");
2651 if (pidXwait(child_id
, NULL
)) {
2657 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2658 *file
= ce
->ce_file
;
2659 return fileno(ce
->ce_fp
);
2663 /* Multipart parts might have content before the first subpart and/or
2664 after the last subpart that hasn't been stored anywhere else, so do
2667 get_leftover_mp_content (CT ct
, int before
/* or after */)
2669 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2671 bool found_boundary
= false;
2677 char *content
= NULL
;
2679 if (! m
) return NOTOK
;
2682 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2684 /* Isolate the beginning of this part to the beginning of the
2685 first subpart and save any content between them. */
2686 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2687 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2688 boundary
= concat ("--", m
->mp_start
, NULL
);
2690 struct part
*last_subpart
= NULL
;
2691 struct part
*subpart
;
2693 /* Go to the last subpart to get its end position. */
2694 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2695 last_subpart
= subpart
;
2698 if (last_subpart
== NULL
) return NOTOK
;
2700 /* Isolate the end of the last subpart to the end of this part
2701 and save any content between them. */
2702 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2703 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2704 boundary
= concat ("--", m
->mp_stop
, NULL
);
2707 /* Back up by 1 to pick up the newline. */
2708 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2710 /* Don't look beyond beginning of first subpart (before) or
2711 next part (after). */
2712 if (read
> max
) bufp
[read
-max
] = '\0';
2715 if (! strcmp (bufp
, boundary
)) {
2716 found_boundary
= true;
2719 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2720 found_boundary
= true;
2725 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2727 char *old_content
= content
;
2728 content
= concat (content
, bufp
, NULL
);
2732 ? concat ("\n", bufp
, NULL
)
2733 : concat (bufp
, NULL
);
2738 if (found_boundary
|| read
> max
) break;
2740 if (read
> max
) break;
2744 /* Skip the newline if that's all there is. */
2748 /* Remove trailing newline, except at EOF. */
2749 if ((before
|| ! feof (ct
->c_fp
)) &&
2750 (cp
= content
+ strlen (content
)) > content
&&
2755 if (strlen (content
) > 1) {
2757 m
->mp_content_before
= content
;
2759 m
->mp_content_after
= content
;
2774 ct_type_str (int type
)
2777 case CT_APPLICATION
:
2778 return "application";
2794 return "unknown_type";
2800 ct_subtype_str (int type
, int subtype
)
2803 case CT_APPLICATION
:
2805 case APPLICATION_OCTETS
:
2807 case APPLICATION_POSTSCRIPT
:
2808 return "postscript";
2810 return "unknown_app_subtype";
2814 case MESSAGE_RFC822
:
2816 case MESSAGE_EXTERNAL
:
2819 return "unknown_msg_subtype";
2825 case MULTI_ALTERNATE
:
2826 return "alternative";
2829 case MULTI_PARALLEL
:
2834 return "unknown_multipart_subtype";
2845 return "unknown_text_subtype";
2848 return "unknown_type";
2854 ct_str_type (const char *type
)
2856 struct str2init
*s2i
;
2858 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
2859 if (! strcasecmp (type
, s2i
->si_key
)) {
2863 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
2872 ct_str_subtype (int type
, const char *subtype
)
2877 case CT_APPLICATION
:
2878 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
2879 if (! strcasecmp (subtype
, kv
->kv_key
)) {
2883 return kv
->kv_value
;
2885 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
2886 if (! strcasecmp (subtype
, kv
->kv_key
)) {
2890 return kv
->kv_value
;
2892 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
2893 if (! strcasecmp (subtype
, kv
->kv_key
)) {
2897 return kv
->kv_value
;
2899 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
2900 if (! strcasecmp (subtype
, kv
->kv_key
)) {
2904 return kv
->kv_value
;
2911 /* Find the content type and InitFunc for the CT. */
2912 const struct str2init
*
2913 get_ct_init (int type
)
2915 const struct str2init
*sp
;
2917 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
2918 if (type
== sp
->si_val
) {
2927 ce_str (int encoding
)
2933 return "quoted-printable";
2949 /* Find the content type and InitFunc for the content encoding method. */
2950 const struct str2init
*
2951 get_ce_method (const char *method
)
2953 struct str2init
*sp
;
2955 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
2956 if (! strcasecmp (method
, sp
->si_key
)) {
2965 * Parse a series of MIME attributes (or parameters) given a header as
2968 * Arguments include:
2970 * filename - Name of input file (for error messages)
2971 * fieldname - Name of field being processed
2972 * headerp - Pointer to pointer of the beginning of the MIME attributes.
2973 * Updated to point to end of attributes when finished.
2974 * param_head - Pointer to head of parameter list
2975 * param_tail - Pointer to tail of parameter list
2976 * commentp - Pointer to header comment pointer (may be NULL)
2978 * Returns OK if parsing was successful, NOTOK if parsing failed, and
2979 * DONE to indicate a benign error (minor parsing error, but the program
2984 parse_header_attrs (const char *filename
, const char *fieldname
,
2985 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
2988 char *cp
= *header_attrp
;
2994 struct sectlist
*next
;
3000 struct sectlist
*sechead
;
3001 struct parmlist
*next
;
3002 } *pp
, *pp2
, *phead
= NULL
;
3004 while (*cp
== ';') {
3005 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3006 bool encoded
= false;
3007 bool partial
= false;
3008 int len
= 0, index
= 0;
3011 while (isspace ((unsigned char) *cp
))
3015 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3020 if (! suppress_extraneous_trailing_semicolon_warning
) {
3021 inform("extraneous trailing ';' in message %s's %s: "
3022 "parameter list", filename
, fieldname
);
3027 /* down case the attribute name */
3028 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3029 *dp
= tolower ((unsigned char) *dp
);
3031 for (up
= dp
; isspace ((unsigned char) *dp
);)
3033 if (dp
== cp
|| *dp
!= '=') {
3034 inform("invalid parameter in message %s's %s: field\n"
3035 " parameter %s (error detected at offset %ld)",
3036 filename
, fieldname
, cp
, (long)(dp
- cp
));
3041 * To handle RFC 2231, we have to deal with the following extensions:
3043 * name*=encoded-value
3044 * name*<N>=part-N-of-a-parameter-value
3045 * name*<N>*=encoded-part-N-of-a-parameter-value
3048 * If there's a * right before the equal sign, it's encoded.
3049 * If there's a * and one or more digits, then it's section N.
3051 * Remember we can have one or the other, or both. cp points to
3052 * beginning of name, up points past the last character in the
3056 for (vp
= cp
; vp
< up
; vp
++) {
3057 if (*vp
== '*' && vp
< up
- 1) {
3061 if (*vp
== '*' && vp
== up
- 1) {
3063 } else if (partial
) {
3064 if (isdigit((unsigned char) *vp
))
3065 index
= *vp
- '0' + index
* 10;
3067 inform("invalid parameter index in message %s's %s: field"
3068 "\n (parameter %s)", filename
, fieldname
, cp
);
3077 * Break out the parameter name and value sections and allocate
3081 nameptr
= mh_xmalloc(len
+ 1);
3082 strncpy(nameptr
, cp
, len
);
3083 nameptr
[len
] = '\0';
3085 for (dp
++; isspace ((unsigned char) *dp
);)
3090 * Single quotes delimit the character set and language tag.
3091 * They are required on the first section (or a complete
3096 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3102 charset
= mh_xmalloc(len
+ 1);
3103 strncpy(charset
, dp
, len
);
3104 charset
[len
] = '\0';
3110 inform("missing charset in message %s's %s: field\n"
3111 " (parameter %s)", filename
, fieldname
, nameptr
);
3117 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3124 lang
= mh_xmalloc(len
+ 1);
3125 strncpy(lang
, dp
, len
);
3132 inform("missing language tag in message %s's %s: field\n"
3133 " (parameter %s)", filename
, fieldname
, nameptr
);
3143 * At this point vp should be pointing at the beginning
3144 * of the encoded value/section. Continue until we reach
3145 * the end or get whitespace. But first, calculate the
3146 * length so we can allocate the correct buffer size.
3149 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3151 if (*(vp
+ 1) == '\0' ||
3152 !isxdigit((unsigned char) *(vp
+ 1)) ||
3153 *(vp
+ 2) == '\0' ||
3154 !isxdigit((unsigned char) *(vp
+ 2))) {
3155 inform("invalid encoded sequence in message %s's %s: field\n"
3156 " (parameter %s)", filename
, fieldname
, nameptr
);
3167 up
= valptr
= mh_xmalloc(len
+ 1);
3169 for (vp
= dp
; istoken(*vp
); vp
++) {
3171 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3182 * A "normal" string. If it's got a leading quote, then we
3183 * strip the quotes out. Otherwise go until we reach the end
3184 * or get whitespace. Note we scan it twice; once to get the
3185 * length, then the second time copies it into the destination
3192 for (cp
= dp
+ 1;;) {
3196 inform("invalid quoted-string in message %s's %s: field\n"
3197 " (parameter %s)", filename
, fieldname
, nameptr
);
3217 for (cp
= dp
; istoken (*cp
); cp
++) {
3222 valptr
= mh_xmalloc(len
+ 1);
3226 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3234 strncpy(valptr
, cp
= dp
, len
);
3242 * If 'partial' is set, we don't allocate a parameter now. We
3243 * put it on the parameter linked list to be reassembled later.
3245 * "phead" points to a list of all parameters we need to reassemble.
3246 * Each parameter has a list of sections. We insert the sections in
3251 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3252 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3267 * Insert this into the section linked list
3275 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3276 sp
->next
= pp
->sechead
;
3279 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3280 if (sp2
->index
== sp
->index
) {
3281 inform("duplicate index (%d) in message %s's %s: field"
3282 "\n (parameter %s)", sp
->index
, filename
,
3283 fieldname
, nameptr
);
3286 if (sp2
->index
< sp
->index
&&
3287 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3288 sp
->next
= sp2
->next
;
3295 inform("Internal error: cannot insert partial param "
3296 "in message %s's %s: field\n (parameter %s)",
3297 filename
, fieldname
, nameptr
);
3303 * Save our charset and lang tags.
3306 if (index
== 0 && encoded
) {
3308 pp
->charset
= charset
;
3313 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3314 pm
->pm_charset
= charset
;
3318 while (isspace ((unsigned char) *cp
))
3322 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3328 * Now that we're done, reassemble all of the partial parameters.
3331 for (pp
= phead
; pp
!= NULL
; ) {
3335 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3336 if (sp
->index
!= pindex
++) {
3337 inform("missing section %d for parameter in message "
3338 "%s's %s: field\n (parameter %s)", pindex
- 1,
3339 filename
, fieldname
, pp
->name
);
3345 p
= q
= mh_xmalloc(tlen
+ 1);
3346 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3347 memcpy(q
, sp
->value
, sp
->len
);
3357 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3358 pm
->pm_charset
= pp
->charset
;
3359 pm
->pm_lang
= pp
->lang
;
3370 * Return the charset for a particular content type.
3374 content_charset (CT ct
)
3376 char *ret_charset
= NULL
;
3378 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3380 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3385 * Create a string based on a list of output parameters. Assume that this
3386 * parameter string will be appended to an existing header, so start out
3387 * with the separator (;). Perform RFC 2231 encoding when necessary.
3391 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3393 char *paramout
= NULL
;
3394 char line
[CPERLIN
* 2], *q
;
3395 int curlen
, index
, cont
, encode
, i
;
3396 size_t valoff
, numchars
;
3398 while (params
!= NULL
) {
3404 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3407 if (strlen(params
->pm_name
) > CPERLIN
) {
3408 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3413 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3416 * Loop until we get a parameter that fits within a line. We
3417 * assume new lines start with a tab, so check our overflow based
3427 * At this point we're definitely continuing the line, so
3428 * be sure to include the parameter name and section index.
3431 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3432 params
->pm_name
, index
);
3435 * Both of these functions do a NUL termination
3439 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3440 numchars
, valoff
, index
);
3442 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3452 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3457 * "line" starts with a ;\n\t, so that doesn't count against
3458 * the length. But add 8 since it starts with a tab; that's
3459 * how we end up with 5.
3462 initialwidth
= strlen(line
) + 5;
3465 * At this point the line should be built, so add it to our
3466 * current output buffer.
3469 paramout
= add(line
, paramout
);
3473 * If this won't fit on the line, start a new one. Save room in
3474 * case we need a semicolon on the end
3477 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3489 * At this point, we're either finishing a continued parameter, or
3490 * we're working on a new one.
3494 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3495 params
->pm_name
, index
);
3497 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3502 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3503 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3505 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3506 strlen(params
->pm_value
+ valoff
), valoff
);
3513 paramout
= add(line
, paramout
);
3514 initialwidth
+= strlen(line
);
3516 params
= params
->pm_next
;
3520 *offsetout
= initialwidth
;
3526 * Calculate the size of a parameter.
3530 * pm - The parameter being output
3531 * index - If continuing the parameter, the index of the section
3533 * valueoff - The current offset into the parameter value that we're
3534 * working on (previous sections have consumed valueoff bytes).
3535 * encode - Set if we should perform encoding on this parameter section
3536 * (given that we're consuming bytesfit bytes).
3537 * cont - Set if the remaining data in value will not fit on a single
3538 * line and will need to be continued.
3539 * bytesfit - The number of bytes that we can consume from the parameter
3540 * value and still fit on a completely new line. The
3541 * calculation assumes the new line starts with a tab,
3542 * includes the parameter name and any encoding, and fits
3543 * within CPERLIN bytes. Will always be at least 1.
3547 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3550 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3551 size_t len
= 0, fit
= 0;
3552 int fitlimit
= 0, eightbit
, maxfit
;
3557 * Add up the length. First, start with the parameter name.
3560 len
= strlen(pm
->pm_name
);
3563 * Scan the parameter value and see if we need to do encoding for this
3567 eightbit
= contains8bit(start
, NULL
);
3570 * Determine if we need to encode this section. Encoding is necessary if:
3572 * - There are any 8-bit characters at all and we're on the first
3574 * - There are 8-bit characters within N bytes of our section start.
3575 * N is calculated based on the number of bytes it would take to
3576 * reach CPERLIN. Specifically:
3577 * 8 (starting tab) +
3578 * strlen(param name) +
3579 * 4 ('* for section marker, '=', opening/closing '"')
3581 * is the number of bytes used by everything that isn't part of the
3582 * value. So that gets subtracted from CPERLIN.
3585 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3586 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3587 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3591 len
++; /* Add in equal sign */
3595 * We're using maxfit as a marker for how many characters we can
3596 * fit into the line. Bump it by two because we're not using quotes
3603 * If we don't have a charset or language tag in this parameter,
3607 if (! pm
->pm_charset
) {
3608 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3609 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3610 die("8-bit characters in parameter \"%s\", but "
3611 "local character set is US-ASCII", pm
->pm_name
);
3614 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3616 len
++; /* For the encoding marker */
3619 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3624 * We know we definitely need to include an index. maxfit already
3625 * includes the section marker.
3627 len
+= strlen(indexchar
);
3629 for (p
= start
; *p
!= '\0'; p
++) {
3630 if (isparamencode(*p
)) {
3638 * Just so there's no confusion: maxfit is counting OUTPUT
3639 * characters (post-encoding). fit is counting INPUT characters.
3641 if (! fitlimit
&& maxfit
>= 0)
3643 else if (! fitlimit
)
3648 * Calculate the string length, but add room for quoting \
3649 * and " if necessary. Also account for quotes at beginning
3652 for (p
= start
; *p
!= '\0'; p
++) {
3663 if (! fitlimit
&& maxfit
>= 0)
3665 else if (! fitlimit
)
3682 * Output an encoded parameter string.
3686 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3687 size_t valueoff
, int index
)
3689 size_t outlen
= 0, n
;
3690 char *endptr
= output
+ len
, *p
;
3693 * First, output the marker for an encoded string.
3701 * If the index is 0, output the character set and language tag.
3702 * If theses were NULL, they should have already been filled in
3707 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3711 if (output
> endptr
) {
3712 inform("Internal error: parameter buffer overflow");
3718 * Copy over the value, encoding if necessary
3721 p
= pm
->pm_value
+ valueoff
;
3722 while (valuelen
-- > 0) {
3723 if (isparamencode(*p
)) {
3724 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
3731 if (output
> endptr
) {
3732 inform("Internal error: parameter buffer overflow");
3743 * Output a "normal" parameter, without encoding. Be sure to escape
3744 * quotes and backslashes if necessary.
3748 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3752 char *endptr
= output
+ len
, *p
;
3758 p
= pm
->pm_value
+ valueoff
;
3760 while (valuelen
-- > 0) {
3771 if (output
> endptr
) {
3772 inform("Internal error: parameter buffer overflow");
3777 if (output
- 2 > endptr
) {
3778 inform("Internal error: parameter buffer overflow");
3789 * Add a parameter to the parameter linked list
3793 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
3798 pm
->pm_name
= nocopy
? name
: getcpy(name
);
3799 pm
->pm_value
= nocopy
? value
: getcpy(value
);
3802 (*last
)->pm_next
= pm
;
3813 * Either replace a current parameter with a new value, or add the parameter
3814 * to the parameter linked list.
3818 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
3822 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
3823 if (strcasecmp(name
, pm
->pm_name
) == 0) {
3825 * If nocopy is set, it's assumed that we own both name
3826 * and value. We don't need name, so we discard it now.
3831 pm
->pm_value
= nocopy
? value
: getcpy(value
);
3836 return add_param(first
, last
, name
, value
, nocopy
);
3840 * Retrieve a parameter value from a parameter linked list. If the parameter
3841 * value needs converted to the local character set, do that now.
3845 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
3847 while (first
!= NULL
) {
3848 if (strcasecmp(name
, first
->pm_name
) == 0) {
3850 return first
->pm_value
;
3851 return getcpy(get_param_value(first
, replace
));
3853 first
= first
->pm_next
;
3860 * Return a parameter value, converting to the local character set if
3865 get_param_value(PM pm
, char replace
)
3867 static char buffer
[4096]; /* I hope no parameters are larger */
3868 size_t bufsize
= sizeof(buffer
);
3873 ICONV_CONST
char *p
;
3874 #else /* HAVE_ICONV */
3876 #endif /* HAVE_ICONV */
3881 * If we don't have a character set indicated, it's assumed to be
3882 * US-ASCII. If it matches our character set, we don't need to convert
3886 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
3887 strlen(pm
->pm_charset
))) {
3888 return pm
->pm_value
;
3892 * In this case, we need to convert. If we have iconv support, use
3893 * that. Otherwise, go through and simply replace every non-ASCII
3894 * character with the substitution character.
3899 bufsize
= sizeof(buffer
);
3900 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
3902 cd
= iconv_open(get_charset(), pm
->pm_charset
);
3903 if (cd
== (iconv_t
) -1) {
3907 inbytes
= strlen(pm
->pm_value
);
3911 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
3912 if (errno
!= EILSEQ
) {
3917 * Reset shift state, substitute our character,
3918 * try to restart conversion.
3921 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
3934 for (++p
, --inbytes
;
3935 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
3954 #endif /* HAVE_ICONV */
3957 * Take everything non-ASCII and substitute the replacement character
3961 bufsize
= sizeof(buffer
);
3962 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
3963 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))