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] != '-')
1157 if (strcmp (bufp
+ 2, m
->mp_start
))
1162 next
= &part
->mp_next
;
1164 if (!(p
= get_content (fp
, ct
->c_file
,
1165 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1173 fseek (fp
, pos
, SEEK_SET
);
1176 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1180 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1181 if (p
->c_end
< p
->c_begin
)
1182 p
->c_begin
= p
->c_end
;
1187 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1192 if (! suppress_bogus_mp_content_warning
) {
1193 inform("bogus multipart content in message %s", ct
->c_file
);
1195 bogus_mp_content
= true;
1197 if (!inout
&& part
) {
1199 p
->c_end
= ct
->c_end
;
1201 if (p
->c_begin
>= p
->c_end
) {
1202 for (next
= &m
->mp_parts
; *next
!= part
;
1203 next
= &((*next
)->mp_next
))
1212 /* reverse the order of the parts for multipart/alternative */
1213 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1219 * label all subparts with part number, and
1220 * then initialize the content of the subpart.
1225 char partnam
[BUFSIZ
];
1228 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1229 pp
= partnam
+ strlen (partnam
);
1234 for (part
= m
->mp_parts
, partnum
= 1; part
;
1235 part
= part
->mp_next
, partnum
++) {
1238 sprintf (pp
, "%d", partnum
);
1239 p
->c_partno
= mh_xstrdup(partnam
);
1241 /* initialize the content of the subparts */
1242 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1251 get_leftover_mp_content (ct
, 1);
1252 get_leftover_mp_content (ct
, 0);
1262 * reverse the order of the parts of a multipart/alternative,
1263 * presumably to put the "most favored" alternative first, for
1264 * ease of choosing/displaying it later on. from a mail message on
1265 * nmh-workers, from kenh:
1266 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1267 * see code in mhn that did the same thing... According to the RCS
1268 * logs, that code was around from the initial checkin of mhn.c by
1269 * John Romine in 1992, which is as far back as we have."
1272 reverse_parts (CT ct
)
1274 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1278 /* Reverse the order of its parts by walking the mp_parts list
1279 and pushing each node to the front. */
1280 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1281 next
= part
->mp_next
;
1282 part
->mp_next
= m
->mp_parts
;
1288 move_preferred_part(CT ct
, mime_type_subtype
*pref
)
1290 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1291 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1295 /* move the matching part(s) to the head of the list: walk the
1296 * list of parts, move matching parts to a new list (maintaining
1297 * their order), and finally, concatenate the old list onto the
1304 head
->mp_next
= m
->mp_parts
;
1305 nhead
->mp_next
= NULL
;
1309 part
= head
->mp_next
;
1310 while (part
!= NULL
) {
1311 ci
= &part
->mp_part
->c_ctinfo
;
1312 if (!strcasecmp(ci
->ci_type
, pref
->type
) &&
1314 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1315 prev
->mp_next
= part
->mp_next
;
1316 part
->mp_next
= NULL
;
1317 ntail
->mp_next
= part
;
1319 part
= prev
->mp_next
;
1322 part
= prev
->mp_next
;
1325 ntail
->mp_next
= head
->mp_next
;
1326 m
->mp_parts
= nhead
->mp_next
;
1330 * move parts that match the user's preferences (-prefer) to the head
1331 * of the line. process preferences in reverse so first one given
1332 * ends up first in line
1338 for (i
= 0; i
< npreferred
; i
++)
1339 move_preferred_part(ct
, mime_preference
+ i
);
1344 /* parse_mime() arranges alternates in reverse (priority) order. This
1345 function can be used to reverse them back. This will put, for
1346 example, a text/plain part before a text/html part in a
1347 multipart/alternative part, for example, where it belongs. */
1349 reverse_alternative_parts (CT ct
)
1351 if (ct
->c_type
== CT_MULTIPART
) {
1352 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1355 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1359 /* And call recursively on each part of a multipart. */
1360 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1361 reverse_alternative_parts (part
->mp_part
);
1374 CI ci
= &ct
->c_ctinfo
;
1376 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1377 inform("\"%s/%s\" type in message %s should be encoded in "
1378 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1383 /* check for missing subtype */
1384 if (!*ci
->ci_subtype
)
1385 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1388 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1390 switch (ct
->c_subtype
) {
1391 case MESSAGE_RFC822
:
1394 case MESSAGE_PARTIAL
:
1400 ct
->c_ctparams
= (void *) p
;
1402 /* scan for parameters "id", "number", and "total" */
1403 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1404 if (!strcasecmp (pm
->pm_name
, "id")) {
1405 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1408 if (!strcasecmp (pm
->pm_name
, "number")) {
1409 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1410 || p
->pm_partno
< 1) {
1412 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1413 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1414 ct
->c_file
, TYPE_FIELD
);
1419 if (!strcasecmp (pm
->pm_name
, "total")) {
1420 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1429 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1430 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1431 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1437 case MESSAGE_EXTERNAL
:
1445 ct
->c_ctparams
= (void *) e
;
1448 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1449 advise (ct
->c_file
, "unable to open for reading");
1453 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1455 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1463 p
->c_ceopenfnx
= NULL
;
1464 if ((exresult
= params_external (ct
, 0)) != NOTOK
1465 && p
->c_ceopenfnx
== openMail
) {
1469 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1471 content_error (NULL
, ct
,
1472 "empty body for access-type=mail-server");
1476 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1477 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1479 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1481 adios ("failed", "fread");
1484 die("unexpected EOF from fread");
1487 bp
+= cc
, size
-= cc
;
1494 p
->c_end
= p
->c_begin
;
1499 if (exresult
== NOTOK
)
1501 if (e
->eb_flags
== NOTOK
)
1504 switch (p
->c_type
) {
1509 if (p
->c_subtype
!= MESSAGE_RFC822
)
1513 e
->eb_partno
= ct
->c_partno
;
1515 (*p
->c_ctinitfnx
) (p
);
1530 params_external (CT ct
, int composing
)
1533 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1534 CI ci
= &ct
->c_ctinfo
;
1536 ct
->c_ceopenfnx
= NULL
;
1537 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1538 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1539 struct str2init
*s2i
;
1540 CT p
= e
->eb_content
;
1542 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1543 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1546 e
->eb_access
= pm
->pm_value
;
1547 e
->eb_flags
= NOTOK
;
1548 p
->c_encoding
= CE_EXTERNAL
;
1551 e
->eb_access
= s2i
->si_key
;
1552 e
->eb_flags
= s2i
->si_val
;
1553 p
->c_encoding
= CE_EXTERNAL
;
1555 /* Call the Init function for this external type */
1556 if ((*s2i
->si_init
)(p
) == NOTOK
)
1560 if (!strcasecmp (pm
->pm_name
, "name")) {
1561 e
->eb_name
= pm
->pm_value
;
1564 if (!strcasecmp (pm
->pm_name
, "permission")) {
1565 e
->eb_permission
= pm
->pm_value
;
1568 if (!strcasecmp (pm
->pm_name
, "site")) {
1569 e
->eb_site
= pm
->pm_value
;
1572 if (!strcasecmp (pm
->pm_name
, "directory")) {
1573 e
->eb_dir
= pm
->pm_value
;
1576 if (!strcasecmp (pm
->pm_name
, "mode")) {
1577 e
->eb_mode
= pm
->pm_value
;
1580 if (!strcasecmp (pm
->pm_name
, "size")) {
1581 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1584 if (!strcasecmp (pm
->pm_name
, "server")) {
1585 e
->eb_server
= pm
->pm_value
;
1588 if (!strcasecmp (pm
->pm_name
, "subject")) {
1589 e
->eb_subject
= pm
->pm_value
;
1592 if (!strcasecmp (pm
->pm_name
, "url")) {
1594 * According to RFC 2017, we have to remove all whitespace from
1598 char *u
, *p
= pm
->pm_value
;
1599 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1601 for (; *p
!= '\0'; p
++) {
1602 if (! isspace((unsigned char) *p
))
1609 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1610 e
->eb_body
= getcpy (pm
->pm_value
);
1615 if (!e
->eb_access
) {
1616 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1617 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1630 InitApplication (CT ct
)
1632 CI ci
= &ct
->c_ctinfo
;
1635 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1642 * TRANSFER ENCODINGS
1646 init_encoding (CT ct
, OpenCEFunc openfnx
)
1648 ct
->c_ceopenfnx
= openfnx
;
1649 ct
->c_ceclosefnx
= close_encoding
;
1650 ct
->c_cesizefnx
= size_encoding
;
1657 close_encoding (CT ct
)
1659 CE ce
= &ct
->c_cefile
;
1668 static unsigned long
1669 size_encoding (CT ct
)
1674 CE ce
= &ct
->c_cefile
;
1677 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1678 return (long) st
.st_size
;
1681 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1682 return (long) st
.st_size
;
1686 if (ct
->c_encoding
== CE_EXTERNAL
)
1687 return ct
->c_end
- ct
->c_begin
;
1690 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1691 return ct
->c_end
- ct
->c_begin
;
1693 if (fstat (fd
, &st
) != NOTOK
)
1694 size
= (long) st
.st_size
;
1698 (*ct
->c_ceclosefnx
) (ct
);
1710 return init_encoding (ct
, openBase64
);
1715 openBase64 (CT ct
, char **file
)
1719 bool own_ct_fp
= false;
1720 char *cp
, *buffer
= NULL
;
1721 /* sbeck -- handle suffixes */
1723 CE ce
= &ct
->c_cefile
;
1724 unsigned char *decoded
;
1728 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1733 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1734 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1740 if (*file
== NULL
) {
1743 ce
->ce_file
= mh_xstrdup(*file
);
1747 /* sbeck@cise.ufl.edu -- handle suffixes */
1749 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1750 if (ce
->ce_unlink
) {
1751 /* Create temporary file with filename extension. */
1752 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1753 die("unable to create temporary file in %s",
1757 ce
->ce_file
= add (cp
, ce
->ce_file
);
1759 } else if (*file
== NULL
) {
1761 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1762 die("unable to create temporary file in %s",
1765 ce
->ce_file
= mh_xstrdup(tempfile
);
1768 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1769 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1773 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1774 die("internal error(1)");
1776 buffer
= mh_xmalloc (len
+ 1);
1779 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1780 content_error (ct
->c_file
, ct
, "unable to open for reading");
1786 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1789 switch (cc
= read (fd
, cp
, len
)) {
1791 content_error (ct
->c_file
, ct
, "error reading from");
1795 content_error (NULL
, ct
, "premature eof");
1806 /* decodeBase64() requires null-terminated input. */
1809 if (decodeBase64 (buffer
, &decoded
, &decoded_len
,
1810 ct
->c_type
== CT_TEXT
) != OK
)
1815 unsigned char *decoded_p
= decoded
;
1816 for (i
= 0; i
< decoded_len
; ++i
) {
1817 putc (*decoded_p
++, ce
->ce_fp
);
1820 if (ferror (ce
->ce_fp
)) {
1821 content_error (ce
->ce_file
, ct
, "error writing to");
1826 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1828 if (fflush (ce
->ce_fp
)) {
1829 content_error (ce
->ce_file
, ct
, "error writing to");
1833 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1836 *file
= ce
->ce_file
;
1842 return fileno (ce
->ce_fp
);
1849 free_encoding (ct
, 0);
1859 static char hex2nib
[0x80] = {
1860 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1861 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1862 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1863 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1864 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1865 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1866 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1867 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1868 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1869 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1870 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1871 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1872 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1873 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1874 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1875 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1882 return init_encoding (ct
, openQuoted
);
1887 openQuoted (CT ct
, char **file
)
1889 int cc
, len
, quoted
;
1890 bool own_ct_fp
= false;
1896 CE ce
= &ct
->c_cefile
;
1897 /* sbeck -- handle suffixes */
1901 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1906 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1907 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1913 if (*file
== NULL
) {
1916 ce
->ce_file
= mh_xstrdup(*file
);
1920 /* sbeck@cise.ufl.edu -- handle suffixes */
1922 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1923 if (ce
->ce_unlink
) {
1924 /* Create temporary file with filename extension. */
1925 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1926 die("unable to create temporary file in %s",
1930 ce
->ce_file
= add (cp
, ce
->ce_file
);
1932 } else if (*file
== NULL
) {
1934 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1935 die("unable to create temporary file in %s",
1938 ce
->ce_file
= mh_xstrdup(tempfile
);
1941 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1942 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1946 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1947 die("internal error(2)");
1950 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1951 content_error (ct
->c_file
, ct
, "unable to open for reading");
1959 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1961 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
1962 content_error (NULL
, ct
, "premature eof");
1966 if ((cc
= gotlen
) > len
)
1970 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
1971 if (!isspace ((unsigned char) *ep
))
1976 for (; cp
< ep
; cp
++) {
1978 /* in an escape sequence */
1980 /* at byte 1 of an escape sequence */
1981 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
1982 /* next is byte 2 */
1985 /* at byte 2 of an escape sequence */
1987 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
1988 putc (mask
, ce
->ce_fp
);
1989 if (ferror (ce
->ce_fp
)) {
1990 content_error (ce
->ce_file
, ct
, "error writing to");
1993 /* finished escape sequence; next may be literal or a new
1994 * escape sequence */
1997 /* on to next byte */
2001 /* not in an escape sequence */
2003 /* starting an escape sequence, or invalid '='? */
2004 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2005 /* "=\n" soft line break, eat the \n */
2009 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2010 /* We don't have 2 bytes left, so this is an invalid
2011 * escape sequence; just show the raw bytes (below). */
2012 } else if (isxdigit ((unsigned char) cp
[1]) &&
2013 isxdigit ((unsigned char) cp
[2])) {
2014 /* Next 2 bytes are hex digits, making this a valid escape
2015 * sequence; let's decode it (above). */
2019 /* One or both of the next 2 is out of range, making this
2020 * an invalid escape sequence; just show the raw bytes
2024 /* Just show the raw byte. */
2025 putc (*cp
, ce
->ce_fp
);
2026 if (ferror (ce
->ce_fp
)) {
2027 content_error (ce
->ce_file
, ct
, "error writing to");
2033 content_error (NULL
, ct
,
2034 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2038 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2040 if (fflush (ce
->ce_fp
)) {
2041 content_error (ce
->ce_file
, ct
, "error writing to");
2045 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2048 *file
= ce
->ce_file
;
2054 return fileno (ce
->ce_fp
);
2057 free_encoding (ct
, 0);
2074 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2077 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2083 open7Bit (CT ct
, char **file
)
2086 bool own_ct_fp
= false;
2087 char buffer
[BUFSIZ
];
2088 /* sbeck -- handle suffixes */
2091 CE ce
= &ct
->c_cefile
;
2094 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2099 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2100 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2106 if (*file
== NULL
) {
2109 ce
->ce_file
= mh_xstrdup(*file
);
2113 /* sbeck@cise.ufl.edu -- handle suffixes */
2115 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2116 if (ce
->ce_unlink
) {
2117 /* Create temporary file with filename extension. */
2118 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2119 die("unable to create temporary file in %s",
2123 ce
->ce_file
= add (cp
, ce
->ce_file
);
2125 } else if (*file
== NULL
) {
2127 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2128 die("unable to create temporary file in %s",
2131 ce
->ce_file
= mh_xstrdup(tempfile
);
2134 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2135 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2139 if (ct
->c_type
== CT_MULTIPART
) {
2140 CI ci
= &ct
->c_ctinfo
;
2144 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2145 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2146 + 1 + strlen (ci
->ci_subtype
);
2147 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2150 fputs (buffer
, ce
->ce_fp
);
2154 if (ci
->ci_comment
) {
2155 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2156 fputs ("\n\t", ce
->ce_fp
);
2160 putc (' ', ce
->ce_fp
);
2163 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2166 fprintf (ce
->ce_fp
, "\n");
2168 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2170 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2172 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2173 fprintf (ce
->ce_fp
, "\n");
2176 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2177 die("internal error(3)");
2180 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2181 content_error (ct
->c_file
, ct
, "unable to open for reading");
2187 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2189 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2191 content_error (ct
->c_file
, ct
, "error reading from");
2195 content_error (NULL
, ct
, "premature eof");
2203 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2204 advise ("open7Bit", "fwrite");
2206 if (ferror (ce
->ce_fp
)) {
2207 content_error (ce
->ce_file
, ct
, "error writing to");
2212 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2214 if (fflush (ce
->ce_fp
)) {
2215 content_error (ce
->ce_file
, ct
, "error writing to");
2219 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2222 *file
= ce
->ce_file
;
2227 return fileno (ce
->ce_fp
);
2230 free_encoding (ct
, 0);
2244 openExternal (CT ct
, CE ce
, char **file
, int *fd
)
2247 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2252 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2253 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2259 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2263 *file
= ce
->ce_file
;
2264 *fd
= fileno (ce
->ce_fp
);
2275 return init_encoding (ct
, openFile
);
2280 openFile (CT ct
, char **file
)
2283 struct exbody
*e
= ct
->c_ctexbody
;
2284 CE ce
= &ct
->c_cefile
;
2286 switch (openExternal (e
->eb_parent
, ce
, file
, &fd
)) {
2298 content_error (NULL
, ct
, "missing name parameter");
2302 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2305 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2306 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2310 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2311 *file
= ce
->ce_file
;
2312 return fileno (ce
->ce_fp
);
2322 return init_encoding (ct
, openFTP
);
2327 openFTP (CT ct
, char **file
)
2331 char *bp
, *ftp
, *user
, *pass
;
2332 char buffer
[BUFSIZ
];
2334 CE ce
= &ct
->c_cefile
;
2335 static char *username
= NULL
;
2336 static char *password
= NULL
;
2340 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2346 switch (openExternal (e
->eb_parent
, ce
, file
, &fd
)) {
2357 if (!e
->eb_name
|| !e
->eb_site
) {
2358 content_error (NULL
, ct
, "missing %s parameter",
2359 e
->eb_name
? "site": "name");
2363 /* Get the buffer ready to go */
2365 buflen
= sizeof(buffer
);
2368 * Construct the query message for user
2370 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2376 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2382 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2383 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2388 if (e
->eb_size
> 0) {
2389 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2394 snprintf (bp
, buflen
, "? ");
2397 * Now, check the answer
2399 if (!read_yes_or_no_if_tty (buffer
))
2404 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2408 ruserpass (e
->eb_site
, &username
, &password
, 0);
2413 ce
->ce_unlink
= (*file
== NULL
);
2416 ce
->ce_file
= mh_xstrdup(*file
);
2419 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2420 die("unable to create temporary file in %s",
2423 ce
->ce_file
= mh_xstrdup(tempfile
);
2426 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2427 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2436 vec
[vecp
++] = r1bindex (ftp
, '/');
2437 vec
[vecp
++] = e
->eb_site
;
2440 vec
[vecp
++] = e
->eb_dir
;
2441 vec
[vecp
++] = e
->eb_name
;
2442 vec
[vecp
++] = ce
->ce_file
,
2443 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2444 ? "ascii" : "binary";
2452 adios ("fork", "unable to");
2456 close (fileno (ce
->ce_fp
));
2458 fprintf (stderr
, "unable to exec ");
2464 if (pidXwait (child_id
, NULL
)) {
2465 username
= password
= NULL
;
2473 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2474 *file
= ce
->ce_file
;
2475 return fileno (ce
->ce_fp
);
2486 return init_encoding (ct
, openMail
);
2491 openMail (CT ct
, char **file
)
2493 int child_id
, fd
, vecp
;
2495 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2496 struct exbody
*e
= ct
->c_ctexbody
;
2497 CE ce
= &ct
->c_cefile
;
2499 switch (openExternal (e
->eb_parent
, ce
, file
, &fd
)) {
2510 if (!e
->eb_server
) {
2511 content_error (NULL
, ct
, "missing server parameter");
2515 /* Get buffer ready to go */
2517 buflen
= sizeof(buffer
);
2519 /* Now, construct query message */
2520 snprintf (bp
, buflen
, "Retrieve content");
2526 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2532 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2534 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2536 /* Now, check answer */
2537 if (!read_yes_or_no_if_tty (buffer
))
2541 vec
[vecp
++] = r1bindex (mailproc
, '/');
2542 vec
[vecp
++] = e
->eb_server
;
2543 vec
[vecp
++] = "-subject";
2544 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2545 vec
[vecp
++] = "-body";
2546 vec
[vecp
++] = e
->eb_body
;
2552 advise ("fork", "unable to");
2556 execvp (mailproc
, vec
);
2557 fprintf (stderr
, "unable to exec ");
2563 if (pidXwait (child_id
, NULL
) == OK
)
2564 inform("request sent");
2568 if (*file
== NULL
) {
2570 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2571 die("unable to create temporary file in %s",
2574 ce
->ce_file
= mh_xstrdup(tempfile
);
2577 ce
->ce_file
= mh_xstrdup(*file
);
2581 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2582 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2586 /* showproc is for mhshow and mhstore, though mhlist -debug
2587 * prints it, too. */
2588 free(ct
->c_showproc
);
2589 ct
->c_showproc
= mh_xstrdup("true");
2591 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2592 *file
= ce
->ce_file
;
2593 return fileno (ce
->ce_fp
);
2604 return init_encoding (ct
, openURL
);
2609 openURL (CT ct
, char **file
)
2611 struct exbody
*e
= ct
->c_ctexbody
;
2612 CE ce
= &ct
->c_cefile
;
2613 char *urlprog
, *program
;
2615 struct msgs_array args
= { 0, 0, NULL
};
2618 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2622 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2626 switch (openExternal(e
->eb_parent
, ce
, file
, &fd
)) {
2638 content_error(NULL
, ct
, "missing url parameter");
2642 ce
->ce_unlink
= (*file
== NULL
);
2645 ce
->ce_file
= mh_xstrdup(*file
);
2648 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2649 die("unable to create temporary file in %s",
2652 ce
->ce_file
= mh_xstrdup(tempfile
);
2655 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2656 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2660 switch (child_id
= fork()) {
2662 adios ("fork", "unable to");
2666 argsplit_msgarg(&args
, urlprog
, &program
);
2667 app_msgarg(&args
, e
->eb_url
);
2668 app_msgarg(&args
, NULL
);
2669 dup2(fileno(ce
->ce_fp
), 1);
2670 close(fileno(ce
->ce_fp
));
2671 execvp(program
, args
.msgs
);
2672 fprintf(stderr
, "Unable to exec ");
2678 if (pidXwait(child_id
, NULL
)) {
2684 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2685 *file
= ce
->ce_file
;
2686 return fileno(ce
->ce_fp
);
2690 /* Multipart parts might have content before the first subpart and/or
2691 after the last subpart that hasn't been stored anywhere else, so do
2694 get_leftover_mp_content (CT ct
, int before
/* or after */)
2696 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2698 bool found_boundary
= false;
2704 char *content
= NULL
;
2706 if (! m
) return NOTOK
;
2709 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2711 /* Isolate the beginning of this part to the beginning of the
2712 first subpart and save any content between them. */
2713 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2714 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2715 boundary
= concat ("--", m
->mp_start
, NULL
);
2717 struct part
*last_subpart
= NULL
;
2718 struct part
*subpart
;
2720 /* Go to the last subpart to get its end position. */
2721 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2722 last_subpart
= subpart
;
2725 if (last_subpart
== NULL
) return NOTOK
;
2727 /* Isolate the end of the last subpart to the end of this part
2728 and save any content between them. */
2729 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2730 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2731 boundary
= concat ("--", m
->mp_stop
, NULL
);
2734 /* Back up by 1 to pick up the newline. */
2735 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2737 /* Don't look beyond beginning of first subpart (before) or
2738 next part (after). */
2739 if (read
> max
) bufp
[read
-max
] = '\0';
2742 if (! strcmp (bufp
, boundary
)) {
2743 found_boundary
= true;
2746 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2747 found_boundary
= true;
2752 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2754 char *old_content
= content
;
2755 content
= concat (content
, bufp
, NULL
);
2759 ? concat ("\n", bufp
, NULL
)
2760 : concat (bufp
, NULL
);
2765 if (found_boundary
|| read
> max
) break;
2767 if (read
> max
) break;
2771 /* Skip the newline if that's all there is. */
2775 /* Remove trailing newline, except at EOF. */
2776 if ((before
|| ! feof (ct
->c_fp
)) &&
2777 (cp
= content
+ strlen (content
)) > content
&&
2782 if (strlen (content
) > 1) {
2784 m
->mp_content_before
= content
;
2786 m
->mp_content_after
= content
;
2801 ct_type_str (int type
)
2804 case CT_APPLICATION
:
2805 return "application";
2821 return "unknown_type";
2827 ct_subtype_str (int type
, int subtype
)
2830 case CT_APPLICATION
:
2832 case APPLICATION_OCTETS
:
2834 case APPLICATION_POSTSCRIPT
:
2835 return "postscript";
2837 return "unknown_app_subtype";
2841 case MESSAGE_RFC822
:
2843 case MESSAGE_PARTIAL
:
2845 case MESSAGE_EXTERNAL
:
2848 return "unknown_msg_subtype";
2854 case MULTI_ALTERNATE
:
2855 return "alternative";
2858 case MULTI_PARALLEL
:
2863 return "unknown_multipart_subtype";
2874 return "unknown_text_subtype";
2877 return "unknown_type";
2883 ct_str_type (const char *type
)
2885 struct str2init
*s2i
;
2887 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
2888 if (! strcasecmp (type
, s2i
->si_key
)) {
2892 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
2901 ct_str_subtype (int type
, const char *subtype
)
2906 case CT_APPLICATION
:
2907 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
2908 if (! strcasecmp (subtype
, kv
->kv_key
)) {
2912 return kv
->kv_value
;
2914 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
2915 if (! strcasecmp (subtype
, kv
->kv_key
)) {
2919 return kv
->kv_value
;
2921 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
2922 if (! strcasecmp (subtype
, kv
->kv_key
)) {
2926 return kv
->kv_value
;
2928 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
2929 if (! strcasecmp (subtype
, kv
->kv_key
)) {
2933 return kv
->kv_value
;
2940 /* Find the content type and InitFunc for the CT. */
2941 const struct str2init
*
2942 get_ct_init (int type
)
2944 const struct str2init
*sp
;
2946 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
2947 if (type
== sp
->si_val
) {
2956 ce_str (int encoding
)
2962 return "quoted-printable";
2978 /* Find the content type and InitFunc for the content encoding method. */
2979 const struct str2init
*
2980 get_ce_method (const char *method
)
2982 struct str2init
*sp
;
2984 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
2985 if (! strcasecmp (method
, sp
->si_key
)) {
2994 * Parse a series of MIME attributes (or parameters) given a header as
2997 * Arguments include:
2999 * filename - Name of input file (for error messages)
3000 * fieldname - Name of field being processed
3001 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3002 * Updated to point to end of attributes when finished.
3003 * param_head - Pointer to head of parameter list
3004 * param_tail - Pointer to tail of parameter list
3005 * commentp - Pointer to header comment pointer (may be NULL)
3007 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3008 * DONE to indicate a benign error (minor parsing error, but the program
3013 parse_header_attrs (const char *filename
, const char *fieldname
,
3014 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3017 char *cp
= *header_attrp
;
3023 struct sectlist
*next
;
3029 struct sectlist
*sechead
;
3030 struct parmlist
*next
;
3031 } *pp
, *pp2
, *phead
= NULL
;
3033 while (*cp
== ';') {
3034 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3035 bool encoded
= false;
3036 bool partial
= false;
3037 int len
= 0, index
= 0;
3040 while (isspace ((unsigned char) *cp
))
3044 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3049 if (! suppress_extraneous_trailing_semicolon_warning
) {
3050 inform("extraneous trailing ';' in message %s's %s: "
3051 "parameter list", filename
, fieldname
);
3056 /* down case the attribute name */
3057 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3058 *dp
= tolower ((unsigned char) *dp
);
3060 for (up
= dp
; isspace ((unsigned char) *dp
);)
3062 if (dp
== cp
|| *dp
!= '=') {
3063 inform("invalid parameter in message %s's %s: field\n"
3064 " parameter %s (error detected at offset %ld)",
3065 filename
, fieldname
, cp
, (long)(dp
- cp
));
3070 * To handle RFC 2231, we have to deal with the following extensions:
3072 * name*=encoded-value
3073 * name*<N>=part-N-of-a-parameter-value
3074 * name*<N>*=encoded-part-N-of-a-parameter-value
3077 * If there's a * right before the equal sign, it's encoded.
3078 * If there's a * and one or more digits, then it's section N.
3080 * Remember we can have one or the other, or both. cp points to
3081 * beginning of name, up points past the last character in the
3085 for (vp
= cp
; vp
< up
; vp
++) {
3086 if (*vp
== '*' && vp
< up
- 1) {
3090 if (*vp
== '*' && vp
== up
- 1) {
3092 } else if (partial
) {
3093 if (isdigit((unsigned char) *vp
))
3094 index
= *vp
- '0' + index
* 10;
3096 inform("invalid parameter index in message %s's %s: field"
3097 "\n (parameter %s)", filename
, fieldname
, cp
);
3106 * Break out the parameter name and value sections and allocate
3110 nameptr
= mh_xmalloc(len
+ 1);
3111 strncpy(nameptr
, cp
, len
);
3112 nameptr
[len
] = '\0';
3114 for (dp
++; isspace ((unsigned char) *dp
);)
3119 * Single quotes delimit the character set and language tag.
3120 * They are required on the first section (or a complete
3125 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3131 charset
= mh_xmalloc(len
+ 1);
3132 strncpy(charset
, dp
, len
);
3133 charset
[len
] = '\0';
3139 inform("missing charset in message %s's %s: field\n"
3140 " (parameter %s)", filename
, fieldname
, nameptr
);
3146 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3153 lang
= mh_xmalloc(len
+ 1);
3154 strncpy(lang
, dp
, len
);
3161 inform("missing language tag in message %s's %s: field\n"
3162 " (parameter %s)", filename
, fieldname
, nameptr
);
3172 * At this point vp should be pointing at the beginning
3173 * of the encoded value/section. Continue until we reach
3174 * the end or get whitespace. But first, calculate the
3175 * length so we can allocate the correct buffer size.
3178 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3180 if (*(vp
+ 1) == '\0' ||
3181 !isxdigit((unsigned char) *(vp
+ 1)) ||
3182 *(vp
+ 2) == '\0' ||
3183 !isxdigit((unsigned char) *(vp
+ 2))) {
3184 inform("invalid encoded sequence in message %s's %s: field\n"
3185 " (parameter %s)", filename
, fieldname
, nameptr
);
3196 up
= valptr
= mh_xmalloc(len
+ 1);
3198 for (vp
= dp
; istoken(*vp
); vp
++) {
3200 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3211 * A "normal" string. If it's got a leading quote, then we
3212 * strip the quotes out. Otherwise go until we reach the end
3213 * or get whitespace. Note we scan it twice; once to get the
3214 * length, then the second time copies it into the destination
3221 for (cp
= dp
+ 1;;) {
3225 inform("invalid quoted-string in message %s's %s: field\n"
3226 " (parameter %s)", filename
, fieldname
, nameptr
);
3246 for (cp
= dp
; istoken (*cp
); cp
++) {
3251 valptr
= mh_xmalloc(len
+ 1);
3255 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3263 strncpy(valptr
, cp
= dp
, len
);
3271 * If 'partial' is set, we don't allocate a parameter now. We
3272 * put it on the parameter linked list to be reassembled later.
3274 * "phead" points to a list of all parameters we need to reassemble.
3275 * Each parameter has a list of sections. We insert the sections in
3280 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3281 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3296 * Insert this into the section linked list
3304 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3305 sp
->next
= pp
->sechead
;
3308 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3309 if (sp2
->index
== sp
->index
) {
3310 inform("duplicate index (%d) in message %s's %s: field"
3311 "\n (parameter %s)", sp
->index
, filename
,
3312 fieldname
, nameptr
);
3315 if (sp2
->index
< sp
->index
&&
3316 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3317 sp
->next
= sp2
->next
;
3324 inform("Internal error: cannot insert partial param "
3325 "in message %s's %s: field\n (parameter %s)",
3326 filename
, fieldname
, nameptr
);
3332 * Save our charset and lang tags.
3335 if (index
== 0 && encoded
) {
3337 pp
->charset
= charset
;
3342 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3343 pm
->pm_charset
= charset
;
3347 while (isspace ((unsigned char) *cp
))
3351 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3357 * Now that we're done, reassemble all of the partial parameters.
3360 for (pp
= phead
; pp
!= NULL
; ) {
3364 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3365 if (sp
->index
!= pindex
++) {
3366 inform("missing section %d for parameter in message "
3367 "%s's %s: field\n (parameter %s)", pindex
- 1,
3368 filename
, fieldname
, pp
->name
);
3374 p
= q
= mh_xmalloc(tlen
+ 1);
3375 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3376 memcpy(q
, sp
->value
, sp
->len
);
3386 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3387 pm
->pm_charset
= pp
->charset
;
3388 pm
->pm_lang
= pp
->lang
;
3399 * Return the charset for a particular content type.
3403 content_charset (CT ct
)
3405 char *ret_charset
= NULL
;
3407 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3409 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3414 * Create a string based on a list of output parameters. Assume that this
3415 * parameter string will be appended to an existing header, so start out
3416 * with the separator (;). Perform RFC 2231 encoding when necessary.
3420 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3422 char *paramout
= NULL
;
3423 char line
[CPERLIN
* 2], *q
;
3424 int curlen
, index
, cont
, encode
, i
;
3425 size_t valoff
, numchars
;
3427 while (params
!= NULL
) {
3433 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3436 if (strlen(params
->pm_name
) > CPERLIN
) {
3437 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3442 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3445 * Loop until we get a parameter that fits within a line. We
3446 * assume new lines start with a tab, so check our overflow based
3456 * At this point we're definitely continuing the line, so
3457 * be sure to include the parameter name and section index.
3460 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3461 params
->pm_name
, index
);
3464 * Both of these functions do a NUL termination
3468 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3469 numchars
, valoff
, index
);
3471 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3481 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3486 * "line" starts with a ;\n\t, so that doesn't count against
3487 * the length. But add 8 since it starts with a tab; that's
3488 * how we end up with 5.
3491 initialwidth
= strlen(line
) + 5;
3494 * At this point the line should be built, so add it to our
3495 * current output buffer.
3498 paramout
= add(line
, paramout
);
3502 * If this won't fit on the line, start a new one. Save room in
3503 * case we need a semicolon on the end
3506 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3518 * At this point, we're either finishing a continued parameter, or
3519 * we're working on a new one.
3523 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3524 params
->pm_name
, index
);
3526 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3531 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3532 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3534 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3535 strlen(params
->pm_value
+ valoff
), valoff
);
3542 paramout
= add(line
, paramout
);
3543 initialwidth
+= strlen(line
);
3545 params
= params
->pm_next
;
3549 *offsetout
= initialwidth
;
3555 * Calculate the size of a parameter.
3559 * pm - The parameter being output
3560 * index - If continuing the parameter, the index of the section
3562 * valueoff - The current offset into the parameter value that we're
3563 * working on (previous sections have consumed valueoff bytes).
3564 * encode - Set if we should perform encoding on this parameter section
3565 * (given that we're consuming bytesfit bytes).
3566 * cont - Set if the remaining data in value will not fit on a single
3567 * line and will need to be continued.
3568 * bytesfit - The number of bytes that we can consume from the parameter
3569 * value and still fit on a completely new line. The
3570 * calculation assumes the new line starts with a tab,
3571 * includes the parameter name and any encoding, and fits
3572 * within CPERLIN bytes. Will always be at least 1.
3576 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3579 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3580 size_t len
= 0, fit
= 0;
3581 int fitlimit
= 0, eightbit
, maxfit
;
3586 * Add up the length. First, start with the parameter name.
3589 len
= strlen(pm
->pm_name
);
3592 * Scan the parameter value and see if we need to do encoding for this
3596 eightbit
= contains8bit(start
, NULL
);
3599 * Determine if we need to encode this section. Encoding is necessary if:
3601 * - There are any 8-bit characters at all and we're on the first
3603 * - There are 8-bit characters within N bytes of our section start.
3604 * N is calculated based on the number of bytes it would take to
3605 * reach CPERLIN. Specifically:
3606 * 8 (starting tab) +
3607 * strlen(param name) +
3608 * 4 ('* for section marker, '=', opening/closing '"')
3610 * is the number of bytes used by everything that isn't part of the
3611 * value. So that gets subtracted from CPERLIN.
3614 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3615 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3616 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3620 len
++; /* Add in equal sign */
3624 * We're using maxfit as a marker for how many characters we can
3625 * fit into the line. Bump it by two because we're not using quotes
3632 * If we don't have a charset or language tag in this parameter,
3636 if (! pm
->pm_charset
) {
3637 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3638 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3639 die("8-bit characters in parameter \"%s\", but "
3640 "local character set is US-ASCII", pm
->pm_name
);
3643 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3645 len
++; /* For the encoding marker */
3648 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3653 * We know we definitely need to include an index. maxfit already
3654 * includes the section marker.
3656 len
+= strlen(indexchar
);
3658 for (p
= start
; *p
!= '\0'; p
++) {
3659 if (isparamencode(*p
)) {
3667 * Just so there's no confusion: maxfit is counting OUTPUT
3668 * characters (post-encoding). fit is counting INPUT characters.
3670 if (! fitlimit
&& maxfit
>= 0)
3672 else if (! fitlimit
)
3677 * Calculate the string length, but add room for quoting \
3678 * and " if necessary. Also account for quotes at beginning
3681 for (p
= start
; *p
!= '\0'; p
++) {
3692 if (! fitlimit
&& maxfit
>= 0)
3694 else if (! fitlimit
)
3711 * Output an encoded parameter string.
3715 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3716 size_t valueoff
, int index
)
3718 size_t outlen
= 0, n
;
3719 char *endptr
= output
+ len
, *p
;
3722 * First, output the marker for an encoded string.
3730 * If the index is 0, output the character set and language tag.
3731 * If theses were NULL, they should have already been filled in
3736 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3740 if (output
> endptr
) {
3741 inform("Internal error: parameter buffer overflow");
3747 * Copy over the value, encoding if necessary
3750 p
= pm
->pm_value
+ valueoff
;
3751 while (valuelen
-- > 0) {
3752 if (isparamencode(*p
)) {
3753 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
3760 if (output
> endptr
) {
3761 inform("Internal error: parameter buffer overflow");
3772 * Output a "normal" parameter, without encoding. Be sure to escape
3773 * quotes and backslashes if necessary.
3777 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3781 char *endptr
= output
+ len
, *p
;
3787 p
= pm
->pm_value
+ valueoff
;
3789 while (valuelen
-- > 0) {
3800 if (output
> endptr
) {
3801 inform("Internal error: parameter buffer overflow");
3806 if (output
- 2 > endptr
) {
3807 inform("Internal error: parameter buffer overflow");
3818 * Add a parameter to the parameter linked list
3822 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
3827 pm
->pm_name
= nocopy
? name
: getcpy(name
);
3828 pm
->pm_value
= nocopy
? value
: getcpy(value
);
3831 (*last
)->pm_next
= pm
;
3842 * Either replace a current parameter with a new value, or add the parameter
3843 * to the parameter linked list.
3847 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
3851 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
3852 if (strcasecmp(name
, pm
->pm_name
) == 0) {
3854 * If nocopy is set, it's assumed that we own both name
3855 * and value. We don't need name, so we discard it now.
3860 pm
->pm_value
= nocopy
? value
: getcpy(value
);
3865 return add_param(first
, last
, name
, value
, nocopy
);
3869 * Retrieve a parameter value from a parameter linked list. If the parameter
3870 * value needs converted to the local character set, do that now.
3874 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
3876 while (first
!= NULL
) {
3877 if (strcasecmp(name
, first
->pm_name
) == 0) {
3879 return first
->pm_value
;
3880 return getcpy(get_param_value(first
, replace
));
3882 first
= first
->pm_next
;
3889 * Return a parameter value, converting to the local character set if
3894 get_param_value(PM pm
, char replace
)
3896 static char buffer
[4096]; /* I hope no parameters are larger */
3897 size_t bufsize
= sizeof(buffer
);
3902 ICONV_CONST
char *p
;
3903 #else /* HAVE_ICONV */
3905 #endif /* HAVE_ICONV */
3910 * If we don't have a character set indicated, it's assumed to be
3911 * US-ASCII. If it matches our character set, we don't need to convert
3915 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
3916 strlen(pm
->pm_charset
))) {
3917 return pm
->pm_value
;
3921 * In this case, we need to convert. If we have iconv support, use
3922 * that. Otherwise, go through and simply replace every non-ASCII
3923 * character with the substitution character.
3928 bufsize
= sizeof(buffer
);
3929 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
3931 cd
= iconv_open(get_charset(), pm
->pm_charset
);
3932 if (cd
== (iconv_t
) -1) {
3936 inbytes
= strlen(pm
->pm_value
);
3940 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
3941 if (errno
!= EILSEQ
) {
3946 * Reset shift state, substitute our character,
3947 * try to restart conversion.
3950 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
3963 for (++p
, --inbytes
;
3964 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
3983 #endif /* HAVE_ICONV */
3986 * Take everything non-ASCII and substitute the replacement character
3990 bufsize
= sizeof(buffer
);
3991 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
3992 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))