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 { "partial", MESSAGE_PARTIAL
},
93 { "external-body", MESSAGE_EXTERNAL
},
94 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
98 * Structure for APPLICATION messages
100 struct k2v SubApplication
[] = {
101 { "octet-stream", APPLICATION_OCTETS
},
102 { "postscript", APPLICATION_POSTSCRIPT
},
103 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
107 * Mapping of names of CTE types in mhbuild directives
109 static struct k2v EncodingType
[] = {
112 { "q-p", CE_QUOTED
},
113 { "quoted-printable", CE_QUOTED
},
114 { "b64", CE_BASE64
},
115 { "base64", CE_BASE64
},
123 static CT
get_content (FILE *, char *, int);
124 static int get_comment (const char *, const char *, char **, char **);
126 static int InitGeneric (CT
);
127 static int InitText (CT
);
128 static int InitMultiPart (CT
);
129 static void reverse_parts (CT
);
130 static void prefer_parts(CT ct
);
131 static int InitMessage (CT
);
132 static int InitApplication (CT
);
133 static int init_encoding (CT
, OpenCEFunc
);
134 static unsigned long size_encoding (CT
);
135 static int InitBase64 (CT
);
136 static int openBase64 (CT
, char **);
137 static int InitQuoted (CT
);
138 static int openQuoted (CT
, char **);
139 static int Init7Bit (CT
);
140 static int openExternal (CT
, CE
, char **, int *);
141 static int InitFile (CT
);
142 static int openFile (CT
, char **);
143 static int InitFTP (CT
);
144 static int openFTP (CT
, char **);
145 static int InitMail (CT
);
146 static int openMail (CT
, char **);
147 static int get_leftover_mp_content (CT
, int);
148 static int InitURL (CT
);
149 static int openURL (CT
, char **);
150 static int parse_header_attrs (const char *, const char *, char **, PM
*,
152 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
153 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
154 static int get_dispo (char *, CT
, int);
156 struct str2init str2cts
[] = {
157 { "application", CT_APPLICATION
, InitApplication
},
158 { "audio", CT_AUDIO
, InitGeneric
},
159 { "image", CT_IMAGE
, InitGeneric
},
160 { "message", CT_MESSAGE
, InitMessage
},
161 { "multipart", CT_MULTIPART
, InitMultiPart
},
162 { "text", CT_TEXT
, InitText
},
163 { "video", CT_VIDEO
, InitGeneric
},
164 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
165 { NULL
, CT_UNKNOWN
, NULL
},
168 struct str2init str2ces
[] = {
169 { "base64", CE_BASE64
, InitBase64
},
170 { "quoted-printable", CE_QUOTED
, InitQuoted
},
171 { "8bit", CE_8BIT
, Init7Bit
},
172 { "7bit", CE_7BIT
, Init7Bit
},
173 { "binary", CE_BINARY
, Init7Bit
},
174 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
175 { NULL
, CE_UNKNOWN
, NULL
},
179 * NOTE WELL: si_key MUST NOT have value of NOTOK
181 * si_val is 1 if access method is anonymous.
183 struct str2init str2methods
[] = {
184 { "afs", 1, InitFile
},
185 { "anon-ftp", 1, InitFTP
},
186 { "ftp", 0, InitFTP
},
187 { "local-file", 0, InitFile
},
188 { "mail-server", 0, InitMail
},
189 { "url", 0, InitURL
},
195 * Main entry point for parsing a MIME message or file.
196 * It returns the Content structure for the top level
197 * entity in the file.
201 parse_mime (char *file
)
210 bogus_mp_content
= false;
213 * Check if file is actually standard input
215 if ((is_stdin
= !(strcmp (file
, "-")))) {
216 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
218 advise("mhparse", "unable to create temporary file in %s",
222 file
= mh_xstrdup(tfile
);
224 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
225 if (fwrite(buffer
, 1, n
, fp
) != n
) {
226 (void) m_unlink (file
);
227 advise (file
, "error copying to temporary file");
233 if (ferror (stdin
)) {
234 (void) m_unlink (file
);
235 advise ("stdin", "error reading");
239 (void) m_unlink (file
);
240 advise (file
, "error writing");
243 fseek (fp
, 0L, SEEK_SET
);
244 } else if (stat (file
, &statbuf
) == NOTOK
) {
245 advise (file
, "unable to stat");
247 } else if (S_ISDIR(statbuf
.st_mode
)) {
248 /* Don't try to parse a directory. */
249 inform("%s is a directory", file
);
251 } else if ((fp
= fopen (file
, "r")) == NULL
) {
252 advise (file
, "unable to read");
256 if (!(ct
= get_content (fp
, file
, 1))) {
258 (void) m_unlink (file
);
259 inform("unable to decode %s", file
);
264 ct
->c_unlink
= 1; /* temp file to remove */
268 if (ct
->c_end
== 0L) {
269 fseek (fp
, 0L, SEEK_END
);
270 ct
->c_end
= ftell (fp
);
273 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
285 * Main routine for reading/parsing the headers
286 * of a message content.
288 * toplevel = 1 # we are at the top level of the message
289 * toplevel = 0 # we are inside message type or multipart type
290 * # other than multipart/digest
291 * toplevel = -1 # we are inside multipart/digest
292 * NB: on failure we will fclose(in)!
296 get_content (FILE *in
, char *file
, int toplevel
)
299 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
303 m_getfld_state_t gstate
;
305 /* allocate the content structure */
308 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
309 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
312 * Parse the header fields for this
313 * content into a linked list.
315 gstate
= m_getfld_state_init(in
);
316 m_getfld_track_filepos2(&gstate
);
317 for (compnum
= 1;;) {
318 int bufsz
= sizeof buf
;
319 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
324 /* get copies of the buffers */
325 np
= mh_xstrdup(name
);
326 vp
= mh_xstrdup(buf
);
328 /* if necessary, get rest of field */
329 while (state
== FLDPLUS
) {
331 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
332 vp
= add (buf
, vp
); /* add to previous value */
335 /* Now add the header data to the list */
336 add_header (ct
, np
, vp
);
338 /* continue, to see if this isn't the last header field */
339 ct
->c_begin
= ftell (in
) + 1;
343 /* There are two cases. The unusual one is when there is no
344 * blank line between the headers and the body. This is
345 * indicated by the name of the header starting with `:'.
347 * For both cases, normal first, `1' is the desired c_begin
348 * file position for the start of the body, and `2' is the
349 * file position when buf is returned.
351 * f o o : b a r \n \n b o d y \n bufsz = 6
353 * f o o : b a r \n b o d y \n bufsz = 4
356 * For the normal case, bufsz includes the
357 * header-terminating `\n', even though it is not in buf,
358 * but bufsz isn't affected when it's missing in the unusual
360 if (name
[0] == ':') {
361 ct
->c_begin
= ftell(in
) - bufsz
;
363 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
368 ct
->c_begin
= ftell (in
);
373 die("message format error in component #%d", compnum
);
376 die("getfld() returned %d", state
);
379 /* break out of the loop */
382 m_getfld_state_destroy (&gstate
);
385 * Read the content headers. We will parse the
386 * MIME related header fields into their various
387 * structures and set internal flags related to
388 * content type/subtype, etc.
391 hp
= ct
->c_first_hf
; /* start at first header field */
393 /* Get MIME-Version field */
394 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
399 vrsn
= mh_xstrdup(FENDNULL(hp
->value
));
401 /* Now, cleanup this field */
404 while (isspace ((unsigned char) *cp
))
406 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
408 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
409 if (!isspace ((unsigned char) *dp
))
413 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
416 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
419 for (dp
= cp
; istoken (*dp
); dp
++)
423 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
426 inform("message %s has unknown value for %s: field (%s), continuing...",
427 ct
->c_file
, VRSN_FIELD
, cp
);
432 if (! suppress_multiple_mime_version_warning
)
433 inform("message %s has multiple %s: fields",
434 ct
->c_file
, VRSN_FIELD
);
438 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
439 /* Get Content-Type field */
440 struct str2init
*s2i
;
441 CI ci
= &ct
->c_ctinfo
;
443 /* Check if we've already seen a Content-Type header */
445 inform("message %s has multiple %s: fields",
446 ct
->c_file
, TYPE_FIELD
);
450 /* Parse the Content-Type field */
451 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
455 * Set the Init function and the internal
456 * flag for this content type.
458 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
459 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
461 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
463 ct
->c_type
= s2i
->si_val
;
464 ct
->c_ctinitfnx
= s2i
->si_init
;
466 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
467 /* Get Content-Transfer-Encoding field */
469 struct str2init
*s2i
;
472 * Check if we've already seen the
473 * Content-Transfer-Encoding field
476 inform("message %s has multiple %s: fields",
477 ct
->c_file
, ENCODING_FIELD
);
481 /* get copy of this field */
482 ct
->c_celine
= cp
= mh_xstrdup(FENDNULL(hp
->value
));
484 while (isspace ((unsigned char) *cp
))
486 for (dp
= cp
; istoken (*dp
); dp
++)
492 * Find the internal flag and Init function
493 * for this transfer encoding.
495 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
496 if (!strcasecmp (cp
, s2i
->si_key
))
498 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
501 ct
->c_encoding
= s2i
->si_val
;
503 /* Call the Init function for this encoding */
504 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
507 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
508 /* Get Content-ID field */
509 ct
->c_id
= add (hp
->value
, ct
->c_id
);
511 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
512 /* Get Content-Description field */
513 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
515 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
516 /* Get Content-Disposition field */
517 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
522 hp
= hp
->next
; /* next header field */
526 * Check if we saw a Content-Type field.
527 * If not, then assign a default value for
528 * it, and the Init function.
532 * If we are inside a multipart/digest message,
533 * so default type is message/rfc822
536 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
538 ct
->c_type
= CT_MESSAGE
;
539 ct
->c_ctinitfnx
= InitMessage
;
542 * Else default type is text/plain
544 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
546 ct
->c_type
= CT_TEXT
;
547 ct
->c_ctinitfnx
= InitText
;
551 /* Use default Transfer-Encoding, if necessary */
553 ct
->c_encoding
= CE_7BIT
;
566 * small routine to add header field to list
570 add_header (CT ct
, char *name
, char *value
)
574 /* allocate header field structure */
577 /* link data into header structure */
582 /* link header structure into the list */
583 if (ct
->c_first_hf
== NULL
) {
584 ct
->c_first_hf
= hp
; /* this is the first */
587 ct
->c_last_hf
->next
= hp
; /* add it to the end */
596 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
597 * directives. Fills in the information of the CTinfo structure.
600 get_ctinfo (char *cp
, CT ct
, int magic
)
609 /* store copy of Content-Type line */
610 cp
= ct
->c_ctline
= mh_xstrdup(FENDNULL(cp
));
612 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
615 /* change newlines to spaces */
616 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
619 /* trim trailing spaces */
620 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
621 if (!isspace ((unsigned char) *dp
))
626 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
628 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
629 &ci
->ci_comment
) == NOTOK
)
632 for (dp
= cp
; istoken (*dp
); dp
++)
636 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
641 inform("invalid %s: field in message %s (empty type)",
642 TYPE_FIELD
, ct
->c_file
);
645 to_lower(ci
->ci_type
);
647 while (isspace ((unsigned char) *cp
))
650 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
651 &ci
->ci_comment
) == NOTOK
)
656 ci
->ci_subtype
= mh_xstrdup("");
661 while (isspace ((unsigned char) *cp
))
664 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
665 &ci
->ci_comment
) == NOTOK
)
668 for (dp
= cp
; istoken (*dp
); dp
++)
672 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
676 if (!*ci
->ci_subtype
) {
677 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
678 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
681 to_lower(ci
->ci_subtype
);
684 while (isspace ((unsigned char) *cp
))
687 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
688 &ci
->ci_comment
) == NOTOK
)
691 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
692 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
693 &ci
->ci_comment
)) != OK
) {
694 return status
== NOTOK
? NOTOK
: OK
;
698 * Get any <Content-Id> given in buffer
700 if (magic
&& *cp
== '<') {
703 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
704 inform("invalid ID in message %s", ct
->c_file
);
710 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
716 while (isspace ((unsigned char) *cp
))
721 * Get any [Content-Description] given in buffer.
723 if (magic
&& *cp
== '[') {
725 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
729 inform("invalid description in message %s", ct
->c_file
);
737 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
743 while (isspace ((unsigned char) *cp
))
748 * Get any {Content-Disposition} given in buffer.
750 if (magic
&& *cp
== '{') {
752 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
756 inform("invalid disposition in message %s", ct
->c_file
);
764 if (get_dispo(cp
, ct
, 1) != OK
)
770 while (isspace ((unsigned char) *cp
))
775 * Get any extension directives (right now just the content transfer
776 * encoding, but maybe others) that we care about.
779 if (magic
&& *cp
== '*') {
781 * See if it's a CTE we match on
786 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
790 inform("invalid null transfer encoding specification");
797 ct
->c_reqencoding
= CE_UNKNOWN
;
799 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
800 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
801 ct
->c_reqencoding
= kv
->kv_value
;
806 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
807 inform("invalid CTE specification: \"%s\"", dp
);
811 while (isspace ((unsigned char) *cp
))
816 * Check if anything is left over
820 ci
->ci_magic
= mh_xstrdup(cp
);
822 /* If there is a Content-Disposition header and it doesn't
823 have a *filename=, extract it from the magic contents.
824 The r1bindex call skips any leading directory
826 if (ct
->c_dispo_type
&&
827 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
828 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
829 r1bindex(ci
->ci_magic
, '/'), 0);
833 inform("extraneous information in message %s's %s: field\n"
834 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
842 * Parse out a Content-Disposition header. A lot of this is cribbed from
846 get_dispo (char *cp
, CT ct
, int buildflag
)
848 char *dp
, *dispoheader
;
853 * Save the whole copy of the Content-Disposition header, unless we're
854 * processing a mhbuild directive. A NULL c_dispo will be a flag to
855 * mhbuild that the disposition header needs to be generated at that
859 dispoheader
= cp
= mh_xstrdup(FENDNULL(cp
));
861 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
864 /* change newlines to spaces */
865 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
868 /* trim trailing spaces */
869 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
870 if (!isspace ((unsigned char) *dp
))
875 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
877 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
883 for (dp
= cp
; istoken (*dp
); dp
++)
887 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
891 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
894 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
895 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
897 if (status
== NOTOK
) {
902 inform("extraneous information in message %s's %s: field\n (%s)",
903 ct
->c_file
, DISPO_FIELD
, cp
);
909 ct
->c_dispo
= dispoheader
;
916 get_comment (const char *filename
, const char *fieldname
, char **ap
,
921 char c
, buffer
[BUFSIZ
], *dp
;
931 inform("invalid comment in message %s's %s: field",
932 filename
, fieldname
);
937 if ((c
= *cp
++) == '\0')
960 if ((dp
= *commentp
)) {
961 *commentp
= concat (dp
, " ", buffer
, NULL
);
964 *commentp
= mh_xstrdup(buffer
);
968 while (isspace ((unsigned char) *cp
))
979 * Handles content types audio, image, and video.
980 * There's not much to do right here.
988 return OK
; /* not much to do here */
1004 CI ci
= &ct
->c_ctinfo
;
1006 /* check for missing subtype */
1007 if (!*ci
->ci_subtype
)
1008 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1011 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1013 /* allocate text character set structure */
1015 ct
->c_ctparams
= (void *) t
;
1017 /* scan for charset parameter */
1018 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1019 if (!strcasecmp (pm
->pm_name
, "charset"))
1022 /* check if content specified a character set */
1024 chset
= pm
->pm_value
;
1025 t
->tx_charset
= CHARSET_SPECIFIED
;
1027 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1031 * If we can not handle character set natively,
1032 * then check profile for string to modify the
1033 * terminal or display method.
1035 * termproc is for mhshow, though mhlist -debug prints it, too.
1037 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1038 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1039 if ((cp
= context_find (buffer
)))
1040 ct
->c_termproc
= mh_xstrdup(cp
);
1052 InitMultiPart (CT ct
)
1062 struct multipart
*m
;
1063 struct part
*part
, **next
;
1064 CI ci
= &ct
->c_ctinfo
;
1069 * The encoding for multipart messages must be either
1070 * 7bit, 8bit, or binary (per RFC 2045).
1072 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1073 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1074 /* Copy the Content-Transfer-Encoding header field body so we can
1075 remove any trailing whitespace and leading blanks from it. */
1076 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1078 bp
= cte
+ strlen (cte
) - 1;
1079 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1080 for (bp
= cte
; isblank((unsigned char)*bp
); ++bp
) continue;
1082 inform("\"%s/%s\" type in message %s must be encoded in\n"
1083 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1084 "mhfixmsg -fixcte can fix it, or\n"
1085 "manually edit the file and change the \"%s\"\n"
1086 "Content-Transfer-Encoding to one of those. For now, continuing...",
1087 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1094 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1097 * Check for "boundary" parameter, which is
1098 * required for multipart messages.
1101 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1102 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1108 /* complain if boundary parameter is missing */
1110 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1111 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1115 /* allocate primary structure for multipart info */
1117 ct
->c_ctparams
= (void *) m
;
1119 /* check if boundary parameter contains only whitespace characters */
1120 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1123 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1124 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1128 /* remove trailing whitespace from boundary parameter */
1129 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1130 if (!isspace ((unsigned char) *dp
))
1134 /* record boundary separators */
1135 m
->mp_start
= concat (bp
, "\n", NULL
);
1136 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1138 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1139 advise (ct
->c_file
, "unable to open for reading");
1143 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1145 next
= &m
->mp_parts
;
1149 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1154 if (bufp
[0] != '-' || bufp
[1] != '-')
1158 * A bit of a lame hack; if this line ends in \r\n then replace
1159 * the \r\n with just a \n so that the boundary markers will match
1160 * up properly in case this uses "MS-DOS" line endings.
1163 if (gotlen
> 2 && bufp
[gotlen
- 1] == '\n' &&
1164 bufp
[gotlen
- 2] == '\r') {
1166 * Note we don't change getpos here, because it is used to
1167 * calculate multipart offsets.
1169 bufp
[gotlen
- 2] = '\n';
1170 bufp
[gotlen
- 1] = '\0';
1174 if (strcmp (bufp
+ 2, m
->mp_start
))
1179 next
= &part
->mp_next
;
1181 if (!(p
= get_content (fp
, ct
->c_file
,
1182 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1190 fseek (fp
, pos
, SEEK_SET
);
1193 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1197 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1198 if (p
->c_end
< p
->c_begin
)
1199 p
->c_begin
= p
->c_end
;
1204 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1209 if (! suppress_bogus_mp_content_warning
) {
1210 inform("bogus multipart content in message %s", ct
->c_file
);
1212 bogus_mp_content
= true;
1214 if (!inout
&& part
) {
1216 p
->c_end
= ct
->c_end
;
1218 if (p
->c_begin
>= p
->c_end
) {
1219 for (next
= &m
->mp_parts
; *next
!= part
;
1220 next
= &((*next
)->mp_next
))
1229 /* reverse the order of the parts for multipart/alternative */
1230 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1236 * label all subparts with part number, and
1237 * then initialize the content of the subpart.
1242 char partnam
[BUFSIZ
];
1245 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1246 pp
= partnam
+ strlen (partnam
);
1251 for (part
= m
->mp_parts
, partnum
= 1; part
;
1252 part
= part
->mp_next
, partnum
++) {
1255 sprintf (pp
, "%d", partnum
);
1256 p
->c_partno
= mh_xstrdup(partnam
);
1258 /* initialize the content of the subparts */
1259 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1268 get_leftover_mp_content (ct
, 1);
1269 get_leftover_mp_content (ct
, 0);
1279 * reverse the order of the parts of a multipart/alternative,
1280 * presumably to put the "most favored" alternative first, for
1281 * ease of choosing/displaying it later on. from a mail message on
1282 * nmh-workers, from kenh:
1283 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1284 * see code in mhn that did the same thing... According to the RCS
1285 * logs, that code was around from the initial checkin of mhn.c by
1286 * John Romine in 1992, which is as far back as we have."
1289 reverse_parts (CT ct
)
1291 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1295 /* Reverse the order of its parts by walking the mp_parts list
1296 and pushing each node to the front. */
1297 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1298 next
= part
->mp_next
;
1299 part
->mp_next
= m
->mp_parts
;
1305 move_preferred_part(CT ct
, mime_type_subtype
*pref
)
1307 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1308 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1312 /* move the matching part(s) to the head of the list: walk the
1313 * list of parts, move matching parts to a new list (maintaining
1314 * their order), and finally, concatenate the old list onto the
1321 head
->mp_next
= m
->mp_parts
;
1322 nhead
->mp_next
= NULL
;
1326 part
= head
->mp_next
;
1327 while (part
!= NULL
) {
1328 ci
= &part
->mp_part
->c_ctinfo
;
1329 if (!strcasecmp(ci
->ci_type
, pref
->type
) &&
1331 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1332 prev
->mp_next
= part
->mp_next
;
1333 part
->mp_next
= NULL
;
1334 ntail
->mp_next
= part
;
1336 part
= prev
->mp_next
;
1339 part
= prev
->mp_next
;
1342 ntail
->mp_next
= head
->mp_next
;
1343 m
->mp_parts
= nhead
->mp_next
;
1347 * move parts that match the user's preferences (-prefer) to the head
1348 * of the line. process preferences in reverse so first one given
1349 * ends up first in line
1355 for (i
= 0; i
< npreferred
; i
++)
1356 move_preferred_part(ct
, mime_preference
+ i
);
1361 /* parse_mime() arranges alternates in reverse (priority) order. This
1362 function can be used to reverse them back. This will put, for
1363 example, a text/plain part before a text/html part in a
1364 multipart/alternative part, for example, where it belongs. */
1366 reverse_alternative_parts (CT ct
)
1368 if (ct
->c_type
== CT_MULTIPART
) {
1369 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1372 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1376 /* And call recursively on each part of a multipart. */
1377 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1378 reverse_alternative_parts (part
->mp_part
);
1391 CI ci
= &ct
->c_ctinfo
;
1393 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1394 inform("\"%s/%s\" type in message %s should be encoded in "
1395 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1400 /* check for missing subtype */
1401 if (!*ci
->ci_subtype
)
1402 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1405 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1407 switch (ct
->c_subtype
) {
1408 case MESSAGE_RFC822
:
1411 case MESSAGE_PARTIAL
:
1417 ct
->c_ctparams
= (void *) p
;
1419 /* scan for parameters "id", "number", and "total" */
1420 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1421 if (!strcasecmp (pm
->pm_name
, "id")) {
1422 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1425 if (!strcasecmp (pm
->pm_name
, "number")) {
1426 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1427 || p
->pm_partno
< 1) {
1429 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1430 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1431 ct
->c_file
, TYPE_FIELD
);
1436 if (!strcasecmp (pm
->pm_name
, "total")) {
1437 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1446 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1447 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1448 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1454 case MESSAGE_EXTERNAL
:
1462 ct
->c_ctparams
= (void *) e
;
1465 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1466 advise (ct
->c_file
, "unable to open for reading");
1470 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1472 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1480 p
->c_ceopenfnx
= NULL
;
1481 if ((exresult
= params_external (ct
, 0)) != NOTOK
1482 && p
->c_ceopenfnx
== openMail
) {
1486 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1488 content_error (NULL
, ct
,
1489 "empty body for access-type=mail-server");
1493 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1494 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1496 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1498 adios ("failed", "fread");
1501 die("unexpected EOF from fread");
1504 bp
+= cc
, size
-= cc
;
1511 p
->c_end
= p
->c_begin
;
1516 if (exresult
== NOTOK
)
1518 if (e
->eb_flags
== NOTOK
)
1521 switch (p
->c_type
) {
1526 if (p
->c_subtype
!= MESSAGE_RFC822
)
1530 e
->eb_partno
= ct
->c_partno
;
1532 (*p
->c_ctinitfnx
) (p
);
1547 params_external (CT ct
, int composing
)
1550 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1551 CI ci
= &ct
->c_ctinfo
;
1553 ct
->c_ceopenfnx
= NULL
;
1554 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1555 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1556 struct str2init
*s2i
;
1557 CT p
= e
->eb_content
;
1559 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1560 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1563 e
->eb_access
= pm
->pm_value
;
1564 e
->eb_flags
= NOTOK
;
1565 p
->c_encoding
= CE_EXTERNAL
;
1568 e
->eb_access
= s2i
->si_key
;
1569 e
->eb_flags
= s2i
->si_val
;
1570 p
->c_encoding
= CE_EXTERNAL
;
1572 /* Call the Init function for this external type */
1573 if ((*s2i
->si_init
)(p
) == NOTOK
)
1577 if (!strcasecmp (pm
->pm_name
, "name")) {
1578 e
->eb_name
= pm
->pm_value
;
1581 if (!strcasecmp (pm
->pm_name
, "permission")) {
1582 e
->eb_permission
= pm
->pm_value
;
1585 if (!strcasecmp (pm
->pm_name
, "site")) {
1586 e
->eb_site
= pm
->pm_value
;
1589 if (!strcasecmp (pm
->pm_name
, "directory")) {
1590 e
->eb_dir
= pm
->pm_value
;
1593 if (!strcasecmp (pm
->pm_name
, "mode")) {
1594 e
->eb_mode
= pm
->pm_value
;
1597 if (!strcasecmp (pm
->pm_name
, "size")) {
1598 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1601 if (!strcasecmp (pm
->pm_name
, "server")) {
1602 e
->eb_server
= pm
->pm_value
;
1605 if (!strcasecmp (pm
->pm_name
, "subject")) {
1606 e
->eb_subject
= pm
->pm_value
;
1609 if (!strcasecmp (pm
->pm_name
, "url")) {
1611 * According to RFC 2017, we have to remove all whitespace from
1615 char *u
, *p
= pm
->pm_value
;
1616 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1618 for (; *p
!= '\0'; p
++) {
1619 if (! isspace((unsigned char) *p
))
1626 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1627 e
->eb_body
= getcpy (pm
->pm_value
);
1632 if (!e
->eb_access
) {
1633 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1634 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1647 InitApplication (CT ct
)
1649 CI ci
= &ct
->c_ctinfo
;
1652 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1659 * TRANSFER ENCODINGS
1663 init_encoding (CT ct
, OpenCEFunc openfnx
)
1665 ct
->c_ceopenfnx
= openfnx
;
1666 ct
->c_ceclosefnx
= close_encoding
;
1667 ct
->c_cesizefnx
= size_encoding
;
1674 close_encoding (CT ct
)
1676 CE ce
= &ct
->c_cefile
;
1685 static unsigned long
1686 size_encoding (CT ct
)
1691 CE ce
= &ct
->c_cefile
;
1694 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1695 return (long) st
.st_size
;
1698 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1699 return (long) st
.st_size
;
1703 if (ct
->c_encoding
== CE_EXTERNAL
)
1704 return ct
->c_end
- ct
->c_begin
;
1707 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1708 return ct
->c_end
- ct
->c_begin
;
1710 if (fstat (fd
, &st
) != NOTOK
)
1711 size
= (long) st
.st_size
;
1715 (*ct
->c_ceclosefnx
) (ct
);
1727 return init_encoding (ct
, openBase64
);
1732 openBase64 (CT ct
, char **file
)
1736 bool own_ct_fp
= false;
1737 char *cp
, *buffer
= NULL
;
1738 /* sbeck -- handle suffixes */
1740 CE ce
= &ct
->c_cefile
;
1741 unsigned char *decoded
;
1745 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1750 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1751 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1757 if (*file
== NULL
) {
1760 ce
->ce_file
= mh_xstrdup(*file
);
1764 /* sbeck@cise.ufl.edu -- handle suffixes */
1766 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1767 if (ce
->ce_unlink
) {
1768 /* Create temporary file with filename extension. */
1769 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1770 die("unable to create temporary file in %s",
1774 ce
->ce_file
= add (cp
, ce
->ce_file
);
1776 } else if (*file
== NULL
) {
1778 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1779 die("unable to create temporary file in %s",
1782 ce
->ce_file
= mh_xstrdup(tempfile
);
1785 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1786 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1790 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1791 die("internal error(1)");
1793 buffer
= mh_xmalloc (len
+ 1);
1796 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1797 content_error (ct
->c_file
, ct
, "unable to open for reading");
1803 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1806 switch (cc
= read (fd
, cp
, len
)) {
1808 content_error (ct
->c_file
, ct
, "error reading from");
1812 content_error (NULL
, ct
, "premature eof");
1823 /* decodeBase64() requires null-terminated input. */
1826 if (decodeBase64 (buffer
, &decoded
, &decoded_len
,
1827 ct
->c_type
== CT_TEXT
) != OK
)
1832 unsigned char *decoded_p
= decoded
;
1833 for (i
= 0; i
< decoded_len
; ++i
) {
1834 putc (*decoded_p
++, ce
->ce_fp
);
1837 if (ferror (ce
->ce_fp
)) {
1838 content_error (ce
->ce_file
, ct
, "error writing to");
1843 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1845 if (fflush (ce
->ce_fp
)) {
1846 content_error (ce
->ce_file
, ct
, "error writing to");
1850 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1853 *file
= ce
->ce_file
;
1859 return fileno (ce
->ce_fp
);
1866 free_encoding (ct
, 0);
1876 static char hex2nib
[0x80] = {
1877 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1878 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1879 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1880 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1881 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1882 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1883 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1884 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1885 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1886 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1887 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1888 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1889 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1890 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1891 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1892 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1899 return init_encoding (ct
, openQuoted
);
1904 openQuoted (CT ct
, char **file
)
1906 int cc
, len
, quoted
;
1907 bool own_ct_fp
= false;
1913 CE ce
= &ct
->c_cefile
;
1914 /* sbeck -- handle suffixes */
1918 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1923 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1924 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1930 if (*file
== NULL
) {
1933 ce
->ce_file
= mh_xstrdup(*file
);
1937 /* sbeck@cise.ufl.edu -- handle suffixes */
1939 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1940 if (ce
->ce_unlink
) {
1941 /* Create temporary file with filename extension. */
1942 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1943 die("unable to create temporary file in %s",
1947 ce
->ce_file
= add (cp
, ce
->ce_file
);
1949 } else if (*file
== NULL
) {
1951 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1952 die("unable to create temporary file in %s",
1955 ce
->ce_file
= mh_xstrdup(tempfile
);
1958 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1959 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1963 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1964 die("internal error(2)");
1967 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1968 content_error (ct
->c_file
, ct
, "unable to open for reading");
1976 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1978 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
1979 content_error (NULL
, ct
, "premature eof");
1983 if ((cc
= gotlen
) > len
)
1987 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
1988 if (!isspace ((unsigned char) *ep
))
1993 for (; cp
< ep
; cp
++) {
1995 /* in an escape sequence */
1997 /* at byte 1 of an escape sequence */
1998 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
1999 /* next is byte 2 */
2002 /* at byte 2 of an escape sequence */
2004 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2005 putc (mask
, ce
->ce_fp
);
2006 if (ferror (ce
->ce_fp
)) {
2007 content_error (ce
->ce_file
, ct
, "error writing to");
2010 /* finished escape sequence; next may be literal or a new
2011 * escape sequence */
2014 /* on to next byte */
2018 /* not in an escape sequence */
2020 /* starting an escape sequence, or invalid '='? */
2021 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2022 /* "=\n" soft line break, eat the \n */
2026 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2027 /* We don't have 2 bytes left, so this is an invalid
2028 * escape sequence; just show the raw bytes (below). */
2029 } else if (isxdigit ((unsigned char) cp
[1]) &&
2030 isxdigit ((unsigned char) cp
[2])) {
2031 /* Next 2 bytes are hex digits, making this a valid escape
2032 * sequence; let's decode it (above). */
2036 /* One or both of the next 2 is out of range, making this
2037 * an invalid escape sequence; just show the raw bytes
2041 /* Just show the raw byte. */
2042 putc (*cp
, ce
->ce_fp
);
2043 if (ferror (ce
->ce_fp
)) {
2044 content_error (ce
->ce_file
, ct
, "error writing to");
2050 content_error (NULL
, ct
,
2051 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2055 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2057 if (fflush (ce
->ce_fp
)) {
2058 content_error (ce
->ce_file
, ct
, "error writing to");
2062 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2065 *file
= ce
->ce_file
;
2071 return fileno (ce
->ce_fp
);
2074 free_encoding (ct
, 0);
2091 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2094 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2100 open7Bit (CT ct
, char **file
)
2103 bool own_ct_fp
= false;
2104 char buffer
[BUFSIZ
];
2105 /* sbeck -- handle suffixes */
2108 CE ce
= &ct
->c_cefile
;
2111 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2116 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2117 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2123 if (*file
== NULL
) {
2126 ce
->ce_file
= mh_xstrdup(*file
);
2130 /* sbeck@cise.ufl.edu -- handle suffixes */
2132 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2133 if (ce
->ce_unlink
) {
2134 /* Create temporary file with filename extension. */
2135 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2136 die("unable to create temporary file in %s",
2140 ce
->ce_file
= add (cp
, ce
->ce_file
);
2142 } else if (*file
== NULL
) {
2144 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2145 die("unable to create temporary file in %s",
2148 ce
->ce_file
= mh_xstrdup(tempfile
);
2151 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2152 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2156 if (ct
->c_type
== CT_MULTIPART
) {
2157 CI ci
= &ct
->c_ctinfo
;
2161 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2162 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2163 + 1 + strlen (ci
->ci_subtype
);
2164 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2167 fputs (buffer
, ce
->ce_fp
);
2171 if (ci
->ci_comment
) {
2172 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2173 fputs ("\n\t", ce
->ce_fp
);
2177 putc (' ', ce
->ce_fp
);
2180 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2183 fprintf (ce
->ce_fp
, "\n");
2185 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2187 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2189 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2190 fprintf (ce
->ce_fp
, "\n");
2193 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2194 die("internal error(3)");
2197 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2198 content_error (ct
->c_file
, ct
, "unable to open for reading");
2204 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2206 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2208 content_error (ct
->c_file
, ct
, "error reading from");
2212 content_error (NULL
, ct
, "premature eof");
2220 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2221 advise ("open7Bit", "fwrite");
2223 if (ferror (ce
->ce_fp
)) {
2224 content_error (ce
->ce_file
, ct
, "error writing to");
2229 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2231 if (fflush (ce
->ce_fp
)) {
2232 content_error (ce
->ce_file
, ct
, "error writing to");
2236 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2239 *file
= ce
->ce_file
;
2244 return fileno (ce
->ce_fp
);
2247 free_encoding (ct
, 0);
2261 openExternal (CT ct
, CE ce
, char **file
, int *fd
)
2264 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2269 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2270 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2276 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2280 *file
= ce
->ce_file
;
2281 *fd
= fileno (ce
->ce_fp
);
2292 return init_encoding (ct
, openFile
);
2297 openFile (CT ct
, char **file
)
2300 struct exbody
*e
= ct
->c_ctexbody
;
2301 CE ce
= &ct
->c_cefile
;
2303 switch (openExternal (e
->eb_parent
, ce
, file
, &fd
)) {
2315 content_error (NULL
, ct
, "missing name parameter");
2319 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2322 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2323 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2327 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2328 *file
= ce
->ce_file
;
2329 return fileno (ce
->ce_fp
);
2339 return init_encoding (ct
, openFTP
);
2344 openFTP (CT ct
, char **file
)
2348 char *bp
, *ftp
, *user
, *pass
;
2349 char buffer
[BUFSIZ
];
2351 CE ce
= &ct
->c_cefile
;
2352 static char *username
= NULL
;
2353 static char *password
= NULL
;
2357 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2363 switch (openExternal (e
->eb_parent
, ce
, file
, &fd
)) {
2374 if (!e
->eb_name
|| !e
->eb_site
) {
2375 content_error (NULL
, ct
, "missing %s parameter",
2376 e
->eb_name
? "site": "name");
2380 /* Get the buffer ready to go */
2382 buflen
= sizeof(buffer
);
2385 * Construct the query message for user
2387 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2393 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2399 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2400 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2405 if (e
->eb_size
> 0) {
2406 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2411 snprintf (bp
, buflen
, "? ");
2414 * Now, check the answer
2416 if (!read_yes_or_no_if_tty (buffer
))
2421 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2425 ruserpass (e
->eb_site
, &username
, &password
, 0);
2430 ce
->ce_unlink
= (*file
== NULL
);
2433 ce
->ce_file
= mh_xstrdup(*file
);
2436 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2437 die("unable to create temporary file in %s",
2440 ce
->ce_file
= mh_xstrdup(tempfile
);
2443 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2444 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2453 vec
[vecp
++] = r1bindex (ftp
, '/');
2454 vec
[vecp
++] = e
->eb_site
;
2457 vec
[vecp
++] = e
->eb_dir
;
2458 vec
[vecp
++] = e
->eb_name
;
2459 vec
[vecp
++] = ce
->ce_file
,
2460 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2461 ? "ascii" : "binary";
2469 adios ("fork", "unable to");
2473 close (fileno (ce
->ce_fp
));
2475 fprintf (stderr
, "unable to exec ");
2481 if (pidXwait (child_id
, NULL
)) {
2482 username
= password
= NULL
;
2490 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2491 *file
= ce
->ce_file
;
2492 return fileno (ce
->ce_fp
);
2503 return init_encoding (ct
, openMail
);
2508 openMail (CT ct
, char **file
)
2510 int child_id
, fd
, vecp
;
2512 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2513 struct exbody
*e
= ct
->c_ctexbody
;
2514 CE ce
= &ct
->c_cefile
;
2516 switch (openExternal (e
->eb_parent
, ce
, file
, &fd
)) {
2527 if (!e
->eb_server
) {
2528 content_error (NULL
, ct
, "missing server parameter");
2532 /* Get buffer ready to go */
2534 buflen
= sizeof(buffer
);
2536 /* Now, construct query message */
2537 snprintf (bp
, buflen
, "Retrieve content");
2543 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2549 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2551 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2553 /* Now, check answer */
2554 if (!read_yes_or_no_if_tty (buffer
))
2558 vec
[vecp
++] = r1bindex (mailproc
, '/');
2559 vec
[vecp
++] = e
->eb_server
;
2560 vec
[vecp
++] = "-subject";
2561 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2562 vec
[vecp
++] = "-body";
2563 vec
[vecp
++] = e
->eb_body
;
2569 advise ("fork", "unable to");
2573 execvp (mailproc
, vec
);
2574 fprintf (stderr
, "unable to exec ");
2580 if (pidXwait (child_id
, NULL
) == OK
)
2581 inform("request sent");
2585 if (*file
== NULL
) {
2587 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2588 die("unable to create temporary file in %s",
2591 ce
->ce_file
= mh_xstrdup(tempfile
);
2594 ce
->ce_file
= mh_xstrdup(*file
);
2598 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2599 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2603 /* showproc is for mhshow and mhstore, though mhlist -debug
2604 * prints it, too. */
2605 free(ct
->c_showproc
);
2606 ct
->c_showproc
= mh_xstrdup("true");
2608 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2609 *file
= ce
->ce_file
;
2610 return fileno (ce
->ce_fp
);
2621 return init_encoding (ct
, openURL
);
2626 openURL (CT ct
, char **file
)
2628 struct exbody
*e
= ct
->c_ctexbody
;
2629 CE ce
= &ct
->c_cefile
;
2630 char *urlprog
, *program
;
2632 struct msgs_array args
= { 0, 0, NULL
};
2635 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2639 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2643 switch (openExternal(e
->eb_parent
, ce
, file
, &fd
)) {
2655 content_error(NULL
, ct
, "missing url parameter");
2659 ce
->ce_unlink
= (*file
== NULL
);
2662 ce
->ce_file
= mh_xstrdup(*file
);
2665 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2666 die("unable to create temporary file in %s",
2669 ce
->ce_file
= mh_xstrdup(tempfile
);
2672 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2673 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2677 switch (child_id
= fork()) {
2679 adios ("fork", "unable to");
2683 argsplit_msgarg(&args
, urlprog
, &program
);
2684 app_msgarg(&args
, e
->eb_url
);
2685 app_msgarg(&args
, NULL
);
2686 dup2(fileno(ce
->ce_fp
), 1);
2687 close(fileno(ce
->ce_fp
));
2688 execvp(program
, args
.msgs
);
2689 fprintf(stderr
, "Unable to exec ");
2695 if (pidXwait(child_id
, NULL
)) {
2701 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2702 *file
= ce
->ce_file
;
2703 return fileno(ce
->ce_fp
);
2707 /* Multipart parts might have content before the first subpart and/or
2708 after the last subpart that hasn't been stored anywhere else, so do
2711 get_leftover_mp_content (CT ct
, int before
/* or after */)
2713 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2715 bool found_boundary
= false;
2721 char *content
= NULL
;
2723 if (! m
) return NOTOK
;
2726 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2728 /* Isolate the beginning of this part to the beginning of the
2729 first subpart and save any content between them. */
2730 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2731 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2732 boundary
= concat ("--", m
->mp_start
, NULL
);
2734 struct part
*last_subpart
= NULL
;
2735 struct part
*subpart
;
2737 /* Go to the last subpart to get its end position. */
2738 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2739 last_subpart
= subpart
;
2742 if (last_subpart
== NULL
) return NOTOK
;
2744 /* Isolate the end of the last subpart to the end of this part
2745 and save any content between them. */
2746 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2747 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2748 boundary
= concat ("--", m
->mp_stop
, NULL
);
2751 /* Back up by 1 to pick up the newline. */
2752 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2754 /* Don't look beyond beginning of first subpart (before) or
2755 next part (after). */
2756 if (read
> max
) bufp
[read
-max
] = '\0';
2759 if (! strcmp (bufp
, boundary
)) {
2760 found_boundary
= true;
2763 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2764 found_boundary
= true;
2769 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2771 char *old_content
= content
;
2772 content
= concat (content
, bufp
, NULL
);
2776 ? concat ("\n", bufp
, NULL
)
2777 : concat (bufp
, NULL
);
2782 if (found_boundary
|| read
> max
) break;
2784 if (read
> max
) break;
2788 /* Skip the newline if that's all there is. */
2792 /* Remove trailing newline, except at EOF. */
2793 if ((before
|| ! feof (ct
->c_fp
)) &&
2794 (cp
= content
+ strlen (content
)) > content
&&
2799 if (strlen (content
) > 1) {
2801 m
->mp_content_before
= content
;
2803 m
->mp_content_after
= content
;
2818 ct_type_str (int type
)
2821 case CT_APPLICATION
:
2822 return "application";
2838 return "unknown_type";
2844 ct_subtype_str (int type
, int subtype
)
2847 case CT_APPLICATION
:
2849 case APPLICATION_OCTETS
:
2851 case APPLICATION_POSTSCRIPT
:
2852 return "postscript";
2854 return "unknown_app_subtype";
2858 case MESSAGE_RFC822
:
2860 case MESSAGE_PARTIAL
:
2862 case MESSAGE_EXTERNAL
:
2865 return "unknown_msg_subtype";
2871 case MULTI_ALTERNATE
:
2872 return "alternative";
2875 case MULTI_PARALLEL
:
2880 return "unknown_multipart_subtype";
2891 return "unknown_text_subtype";
2894 return "unknown_type";
2900 ct_str_type (const char *type
)
2902 struct str2init
*s2i
;
2904 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
2905 if (! strcasecmp (type
, s2i
->si_key
)) {
2909 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
2918 ct_str_subtype (int type
, const char *subtype
)
2923 case CT_APPLICATION
:
2924 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
2925 if (! strcasecmp (subtype
, kv
->kv_key
)) {
2929 return kv
->kv_value
;
2931 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
2932 if (! strcasecmp (subtype
, kv
->kv_key
)) {
2936 return kv
->kv_value
;
2938 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
2939 if (! strcasecmp (subtype
, kv
->kv_key
)) {
2943 return kv
->kv_value
;
2945 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
2946 if (! strcasecmp (subtype
, kv
->kv_key
)) {
2950 return kv
->kv_value
;
2957 /* Find the content type and InitFunc for the CT. */
2958 const struct str2init
*
2959 get_ct_init (int type
)
2961 const struct str2init
*sp
;
2963 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
2964 if (type
== sp
->si_val
) {
2973 ce_str (int encoding
)
2979 return "quoted-printable";
2995 /* Find the content type and InitFunc for the content encoding method. */
2996 const struct str2init
*
2997 get_ce_method (const char *method
)
2999 struct str2init
*sp
;
3001 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3002 if (! strcasecmp (method
, sp
->si_key
)) {
3011 * Parse a series of MIME attributes (or parameters) given a header as
3014 * Arguments include:
3016 * filename - Name of input file (for error messages)
3017 * fieldname - Name of field being processed
3018 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3019 * Updated to point to end of attributes when finished.
3020 * param_head - Pointer to head of parameter list
3021 * param_tail - Pointer to tail of parameter list
3022 * commentp - Pointer to header comment pointer (may be NULL)
3024 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3025 * DONE to indicate a benign error (minor parsing error, but the program
3030 parse_header_attrs (const char *filename
, const char *fieldname
,
3031 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3034 char *cp
= *header_attrp
;
3040 struct sectlist
*next
;
3046 struct sectlist
*sechead
;
3047 struct parmlist
*next
;
3048 } *pp
, *pp2
, *phead
= NULL
;
3050 while (*cp
== ';') {
3051 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3052 bool encoded
= false;
3053 bool partial
= false;
3054 int len
= 0, index
= 0;
3057 while (isspace ((unsigned char) *cp
))
3061 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3066 if (! suppress_extraneous_trailing_semicolon_warning
) {
3067 inform("extraneous trailing ';' in message %s's %s: "
3068 "parameter list", filename
, fieldname
);
3073 /* down case the attribute name */
3074 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3075 *dp
= tolower ((unsigned char) *dp
);
3077 for (up
= dp
; isspace ((unsigned char) *dp
);)
3079 if (dp
== cp
|| *dp
!= '=') {
3080 inform("invalid parameter in message %s's %s: field\n"
3081 " parameter %s (error detected at offset %ld)",
3082 filename
, fieldname
, cp
, (long)(dp
- cp
));
3087 * To handle RFC 2231, we have to deal with the following extensions:
3089 * name*=encoded-value
3090 * name*<N>=part-N-of-a-parameter-value
3091 * name*<N>*=encoded-part-N-of-a-parameter-value
3094 * If there's a * right before the equal sign, it's encoded.
3095 * If there's a * and one or more digits, then it's section N.
3097 * Remember we can have one or the other, or both. cp points to
3098 * beginning of name, up points past the last character in the
3102 for (vp
= cp
; vp
< up
; vp
++) {
3103 if (*vp
== '*' && vp
< up
- 1) {
3107 if (*vp
== '*' && vp
== up
- 1) {
3109 } else if (partial
) {
3110 if (isdigit((unsigned char) *vp
))
3111 index
= *vp
- '0' + index
* 10;
3113 inform("invalid parameter index in message %s's %s: field"
3114 "\n (parameter %s)", filename
, fieldname
, cp
);
3123 * Break out the parameter name and value sections and allocate
3127 nameptr
= mh_xmalloc(len
+ 1);
3128 strncpy(nameptr
, cp
, len
);
3129 nameptr
[len
] = '\0';
3131 for (dp
++; isspace ((unsigned char) *dp
);)
3136 * Single quotes delimit the character set and language tag.
3137 * They are required on the first section (or a complete
3142 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3148 charset
= mh_xmalloc(len
+ 1);
3149 strncpy(charset
, dp
, len
);
3150 charset
[len
] = '\0';
3156 inform("missing charset in message %s's %s: field\n"
3157 " (parameter %s)", filename
, fieldname
, nameptr
);
3163 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3170 lang
= mh_xmalloc(len
+ 1);
3171 strncpy(lang
, dp
, len
);
3178 inform("missing language tag in message %s's %s: field\n"
3179 " (parameter %s)", filename
, fieldname
, nameptr
);
3189 * At this point vp should be pointing at the beginning
3190 * of the encoded value/section. Continue until we reach
3191 * the end or get whitespace. But first, calculate the
3192 * length so we can allocate the correct buffer size.
3195 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3197 if (*(vp
+ 1) == '\0' ||
3198 !isxdigit((unsigned char) *(vp
+ 1)) ||
3199 *(vp
+ 2) == '\0' ||
3200 !isxdigit((unsigned char) *(vp
+ 2))) {
3201 inform("invalid encoded sequence in message %s's %s: field\n"
3202 " (parameter %s)", filename
, fieldname
, nameptr
);
3213 up
= valptr
= mh_xmalloc(len
+ 1);
3215 for (vp
= dp
; istoken(*vp
); vp
++) {
3217 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3228 * A "normal" string. If it's got a leading quote, then we
3229 * strip the quotes out. Otherwise go until we reach the end
3230 * or get whitespace. Note we scan it twice; once to get the
3231 * length, then the second time copies it into the destination
3238 for (cp
= dp
+ 1;;) {
3242 inform("invalid quoted-string in message %s's %s: field\n"
3243 " (parameter %s)", filename
, fieldname
, nameptr
);
3263 for (cp
= dp
; istoken (*cp
); cp
++) {
3268 valptr
= mh_xmalloc(len
+ 1);
3272 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3280 strncpy(valptr
, cp
= dp
, len
);
3288 * If 'partial' is set, we don't allocate a parameter now. We
3289 * put it on the parameter linked list to be reassembled later.
3291 * "phead" points to a list of all parameters we need to reassemble.
3292 * Each parameter has a list of sections. We insert the sections in
3297 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3298 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3313 * Insert this into the section linked list
3321 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3322 sp
->next
= pp
->sechead
;
3325 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3326 if (sp2
->index
== sp
->index
) {
3327 inform("duplicate index (%d) in message %s's %s: field"
3328 "\n (parameter %s)", sp
->index
, filename
,
3329 fieldname
, nameptr
);
3332 if (sp2
->index
< sp
->index
&&
3333 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3334 sp
->next
= sp2
->next
;
3341 inform("Internal error: cannot insert partial param "
3342 "in message %s's %s: field\n (parameter %s)",
3343 filename
, fieldname
, nameptr
);
3349 * Save our charset and lang tags.
3352 if (index
== 0 && encoded
) {
3354 pp
->charset
= charset
;
3359 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3360 pm
->pm_charset
= charset
;
3364 while (isspace ((unsigned char) *cp
))
3368 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3374 * Now that we're done, reassemble all of the partial parameters.
3377 for (pp
= phead
; pp
!= NULL
; ) {
3381 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3382 if (sp
->index
!= pindex
++) {
3383 inform("missing section %d for parameter in message "
3384 "%s's %s: field\n (parameter %s)", pindex
- 1,
3385 filename
, fieldname
, pp
->name
);
3391 p
= q
= mh_xmalloc(tlen
+ 1);
3392 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3393 memcpy(q
, sp
->value
, sp
->len
);
3403 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3404 pm
->pm_charset
= pp
->charset
;
3405 pm
->pm_lang
= pp
->lang
;
3416 * Return the charset for a particular content type.
3420 content_charset (CT ct
)
3422 char *ret_charset
= NULL
;
3424 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3426 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3431 * Create a string based on a list of output parameters. Assume that this
3432 * parameter string will be appended to an existing header, so start out
3433 * with the separator (;). Perform RFC 2231 encoding when necessary.
3437 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3439 char *paramout
= NULL
;
3440 char line
[CPERLIN
* 2], *q
;
3441 int curlen
, index
, cont
, encode
, i
;
3442 size_t valoff
, numchars
;
3444 while (params
!= NULL
) {
3450 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3453 if (strlen(params
->pm_name
) > CPERLIN
) {
3454 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3459 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3462 * Loop until we get a parameter that fits within a line. We
3463 * assume new lines start with a tab, so check our overflow based
3473 * At this point we're definitely continuing the line, so
3474 * be sure to include the parameter name and section index.
3477 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3478 params
->pm_name
, index
);
3481 * Both of these functions do a NUL termination
3485 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3486 numchars
, valoff
, index
);
3488 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3498 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3503 * "line" starts with a ;\n\t, so that doesn't count against
3504 * the length. But add 8 since it starts with a tab; that's
3505 * how we end up with 5.
3508 initialwidth
= strlen(line
) + 5;
3511 * At this point the line should be built, so add it to our
3512 * current output buffer.
3515 paramout
= add(line
, paramout
);
3519 * If this won't fit on the line, start a new one. Save room in
3520 * case we need a semicolon on the end
3523 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3535 * At this point, we're either finishing a continued parameter, or
3536 * we're working on a new one.
3540 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3541 params
->pm_name
, index
);
3543 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3548 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3549 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3551 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3552 strlen(params
->pm_value
+ valoff
), valoff
);
3559 paramout
= add(line
, paramout
);
3560 initialwidth
+= strlen(line
);
3562 params
= params
->pm_next
;
3566 *offsetout
= initialwidth
;
3572 * Calculate the size of a parameter.
3576 * pm - The parameter being output
3577 * index - If continuing the parameter, the index of the section
3579 * valueoff - The current offset into the parameter value that we're
3580 * working on (previous sections have consumed valueoff bytes).
3581 * encode - Set if we should perform encoding on this parameter section
3582 * (given that we're consuming bytesfit bytes).
3583 * cont - Set if the remaining data in value will not fit on a single
3584 * line and will need to be continued.
3585 * bytesfit - The number of bytes that we can consume from the parameter
3586 * value and still fit on a completely new line. The
3587 * calculation assumes the new line starts with a tab,
3588 * includes the parameter name and any encoding, and fits
3589 * within CPERLIN bytes. Will always be at least 1.
3593 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3596 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3597 size_t len
= 0, fit
= 0;
3598 int fitlimit
= 0, eightbit
, maxfit
;
3603 * Add up the length. First, start with the parameter name.
3606 len
= strlen(pm
->pm_name
);
3609 * Scan the parameter value and see if we need to do encoding for this
3613 eightbit
= contains8bit(start
, NULL
);
3616 * Determine if we need to encode this section. Encoding is necessary if:
3618 * - There are any 8-bit characters at all and we're on the first
3620 * - There are 8-bit characters within N bytes of our section start.
3621 * N is calculated based on the number of bytes it would take to
3622 * reach CPERLIN. Specifically:
3623 * 8 (starting tab) +
3624 * strlen(param name) +
3625 * 4 ('* for section marker, '=', opening/closing '"')
3627 * is the number of bytes used by everything that isn't part of the
3628 * value. So that gets subtracted from CPERLIN.
3631 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3632 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3633 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3637 len
++; /* Add in equal sign */
3641 * We're using maxfit as a marker for how many characters we can
3642 * fit into the line. Bump it by two because we're not using quotes
3649 * If we don't have a charset or language tag in this parameter,
3653 if (! pm
->pm_charset
) {
3654 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3655 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3656 die("8-bit characters in parameter \"%s\", but "
3657 "local character set is US-ASCII", pm
->pm_name
);
3660 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3662 len
++; /* For the encoding marker */
3665 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3670 * We know we definitely need to include an index. maxfit already
3671 * includes the section marker.
3673 len
+= strlen(indexchar
);
3675 for (p
= start
; *p
!= '\0'; p
++) {
3676 if (isparamencode(*p
)) {
3684 * Just so there's no confusion: maxfit is counting OUTPUT
3685 * characters (post-encoding). fit is counting INPUT characters.
3687 if (! fitlimit
&& maxfit
>= 0)
3689 else if (! fitlimit
)
3694 * Calculate the string length, but add room for quoting \
3695 * and " if necessary. Also account for quotes at beginning
3698 for (p
= start
; *p
!= '\0'; p
++) {
3709 if (! fitlimit
&& maxfit
>= 0)
3711 else if (! fitlimit
)
3728 * Output an encoded parameter string.
3732 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3733 size_t valueoff
, int index
)
3735 size_t outlen
= 0, n
;
3736 char *endptr
= output
+ len
, *p
;
3739 * First, output the marker for an encoded string.
3747 * If the index is 0, output the character set and language tag.
3748 * If theses were NULL, they should have already been filled in
3753 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3757 if (output
> endptr
) {
3758 inform("Internal error: parameter buffer overflow");
3764 * Copy over the value, encoding if necessary
3767 p
= pm
->pm_value
+ valueoff
;
3768 while (valuelen
-- > 0) {
3769 if (isparamencode(*p
)) {
3770 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
3777 if (output
> endptr
) {
3778 inform("Internal error: parameter buffer overflow");
3789 * Output a "normal" parameter, without encoding. Be sure to escape
3790 * quotes and backslashes if necessary.
3794 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3798 char *endptr
= output
+ len
, *p
;
3804 p
= pm
->pm_value
+ valueoff
;
3806 while (valuelen
-- > 0) {
3817 if (output
> endptr
) {
3818 inform("Internal error: parameter buffer overflow");
3823 if (output
- 2 > endptr
) {
3824 inform("Internal error: parameter buffer overflow");
3835 * Add a parameter to the parameter linked list
3839 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
3844 pm
->pm_name
= nocopy
? name
: getcpy(name
);
3845 pm
->pm_value
= nocopy
? value
: getcpy(value
);
3848 (*last
)->pm_next
= pm
;
3859 * Either replace a current parameter with a new value, or add the parameter
3860 * to the parameter linked list.
3864 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
3868 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
3869 if (strcasecmp(name
, pm
->pm_name
) == 0) {
3871 * If nocopy is set, it's assumed that we own both name
3872 * and value. We don't need name, so we discard it now.
3877 pm
->pm_value
= nocopy
? value
: getcpy(value
);
3882 return add_param(first
, last
, name
, value
, nocopy
);
3886 * Retrieve a parameter value from a parameter linked list. If the parameter
3887 * value needs converted to the local character set, do that now.
3891 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
3893 while (first
!= NULL
) {
3894 if (strcasecmp(name
, first
->pm_name
) == 0) {
3896 return first
->pm_value
;
3897 return getcpy(get_param_value(first
, replace
));
3899 first
= first
->pm_next
;
3906 * Return a parameter value, converting to the local character set if
3911 get_param_value(PM pm
, char replace
)
3913 static char buffer
[4096]; /* I hope no parameters are larger */
3914 size_t bufsize
= sizeof(buffer
);
3919 ICONV_CONST
char *p
;
3920 #else /* HAVE_ICONV */
3922 #endif /* HAVE_ICONV */
3927 * If we don't have a character set indicated, it's assumed to be
3928 * US-ASCII. If it matches our character set, we don't need to convert
3932 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
3933 strlen(pm
->pm_charset
))) {
3934 return pm
->pm_value
;
3938 * In this case, we need to convert. If we have iconv support, use
3939 * that. Otherwise, go through and simply replace every non-ASCII
3940 * character with the substitution character.
3945 bufsize
= sizeof(buffer
);
3946 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
3948 cd
= iconv_open(get_charset(), pm
->pm_charset
);
3949 if (cd
== (iconv_t
) -1) {
3953 inbytes
= strlen(pm
->pm_value
);
3957 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
3958 if (errno
!= EILSEQ
) {
3963 * Reset shift state, substitute our character,
3964 * try to restart conversion.
3967 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
3980 for (++p
, --inbytes
;
3981 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4000 #endif /* HAVE_ICONV */
4003 * Take everything non-ASCII and substitute the replacement character
4007 bufsize
= sizeof(buffer
);
4008 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4009 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))