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 "h/mhcachesbr.h"
31 #include "sbr/m_mktemp.h"
35 #endif /* HAVE_ICONV */
36 #include "sbr/base64.h"
42 * These are for mhfixmsg to:
43 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
45 * 2) Suppress the warning about bogus multipart content, and report it.
46 * 3) Suppress the warning about extraneous trailing ';' in header parameter
49 bool skip_mp_cte_check
;
50 bool suppress_bogus_mp_content_warning
;
51 bool bogus_mp_content
;
52 bool suppress_extraneous_trailing_semicolon_warning
;
55 * By default, suppress warning about multiple MIME-Version header fields.
57 bool suppress_multiple_mime_version_warning
= true;
59 /* list of preferred type/subtype pairs, for -prefer */
60 mime_type_subtype mime_preference
[NPREFS
];
65 * Structures for TEXT messages
67 struct k2v SubText
[] = {
68 { "plain", TEXT_PLAIN
},
69 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC 1341 */
70 { "enriched", TEXT_ENRICHED
}, /* defined in RFC 1896 */
71 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
74 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
77 * Structures for MULTIPART messages
79 struct k2v SubMultiPart
[] = {
80 { "mixed", MULTI_MIXED
},
81 { "alternative", MULTI_ALTERNATE
},
82 { "digest", MULTI_DIGEST
},
83 { "parallel", MULTI_PARALLEL
},
84 { "related", MULTI_RELATED
},
85 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
89 * Structures for MESSAGE messages
91 struct k2v SubMessage
[] = {
92 { "rfc822", MESSAGE_RFC822
},
93 { "partial", MESSAGE_PARTIAL
},
94 { "external-body", MESSAGE_EXTERNAL
},
95 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
99 * Structure for APPLICATION messages
101 struct k2v SubApplication
[] = {
102 { "octet-stream", APPLICATION_OCTETS
},
103 { "postscript", APPLICATION_POSTSCRIPT
},
104 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
108 * Mapping of names of CTE types in mhbuild directives
110 static struct k2v EncodingType
[] = {
113 { "q-p", CE_QUOTED
},
114 { "quoted-printable", CE_QUOTED
},
115 { "b64", CE_BASE64
},
116 { "base64", CE_BASE64
},
124 static CT
get_content (FILE *, char *, int);
125 static int get_comment (const char *, const char *, char **, char **);
127 static int InitGeneric (CT
);
128 static int InitText (CT
);
129 static int InitMultiPart (CT
);
130 static void reverse_parts (CT
);
131 static void prefer_parts(CT ct
);
132 static int InitMessage (CT
);
133 static int InitApplication (CT
);
134 static int init_encoding (CT
, OpenCEFunc
);
135 static unsigned long size_encoding (CT
);
136 static int InitBase64 (CT
);
137 static int openBase64 (CT
, char **);
138 static int InitQuoted (CT
);
139 static int openQuoted (CT
, char **);
140 static int Init7Bit (CT
);
141 static int openExternal (CT
, CT
, CE
, char **, int *);
142 static int InitFile (CT
);
143 static int openFile (CT
, char **);
144 static int InitFTP (CT
);
145 static int openFTP (CT
, char **);
146 static int InitMail (CT
);
147 static int openMail (CT
, char **);
148 static int get_leftover_mp_content (CT
, int);
149 static int InitURL (CT
);
150 static int openURL (CT
, char **);
151 static int parse_header_attrs (const char *, const char *, char **, PM
*,
153 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
154 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
155 static int get_dispo (char *, CT
, int);
157 struct str2init str2cts
[] = {
158 { "application", CT_APPLICATION
, InitApplication
},
159 { "audio", CT_AUDIO
, InitGeneric
},
160 { "image", CT_IMAGE
, InitGeneric
},
161 { "message", CT_MESSAGE
, InitMessage
},
162 { "multipart", CT_MULTIPART
, InitMultiPart
},
163 { "text", CT_TEXT
, InitText
},
164 { "video", CT_VIDEO
, InitGeneric
},
165 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
166 { NULL
, CT_UNKNOWN
, NULL
},
169 struct str2init str2ces
[] = {
170 { "base64", CE_BASE64
, InitBase64
},
171 { "quoted-printable", CE_QUOTED
, InitQuoted
},
172 { "8bit", CE_8BIT
, Init7Bit
},
173 { "7bit", CE_7BIT
, Init7Bit
},
174 { "binary", CE_BINARY
, Init7Bit
},
175 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
176 { NULL
, CE_UNKNOWN
, NULL
},
180 * NOTE WELL: si_key MUST NOT have value of NOTOK
182 * si_val is 1 if access method is anonymous.
184 struct str2init str2methods
[] = {
185 { "afs", 1, InitFile
},
186 { "anon-ftp", 1, InitFTP
},
187 { "ftp", 0, InitFTP
},
188 { "local-file", 0, InitFile
},
189 { "mail-server", 0, InitMail
},
190 { "url", 0, InitURL
},
196 * Main entry point for parsing a MIME message or file.
197 * It returns the Content structure for the top level
198 * entity in the file.
202 parse_mime (char *file
)
211 bogus_mp_content
= false;
214 * Check if file is actually standard input
216 if ((is_stdin
= !(strcmp (file
, "-")))) {
217 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
219 advise("mhparse", "unable to create temporary file in %s",
223 file
= mh_xstrdup(tfile
);
225 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
226 if (fwrite(buffer
, 1, n
, fp
) != n
) {
227 (void) m_unlink (file
);
228 advise (file
, "error copying to temporary file");
234 if (ferror (stdin
)) {
235 (void) m_unlink (file
);
236 advise ("stdin", "error reading");
240 (void) m_unlink (file
);
241 advise (file
, "error writing");
244 fseek (fp
, 0L, SEEK_SET
);
245 } else if (stat (file
, &statbuf
) == NOTOK
) {
246 advise (file
, "unable to stat");
248 } else if (S_ISDIR(statbuf
.st_mode
)) {
249 /* Don't try to parse a directory. */
250 inform("%s is a directory", file
);
252 } else if ((fp
= fopen (file
, "r")) == NULL
) {
253 advise (file
, "unable to read");
257 if (!(ct
= get_content (fp
, file
, 1))) {
259 (void) m_unlink (file
);
260 inform("unable to decode %s", file
);
265 ct
->c_unlink
= 1; /* temp file to remove */
269 if (ct
->c_end
== 0L) {
270 fseek (fp
, 0L, SEEK_END
);
271 ct
->c_end
= ftell (fp
);
274 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
286 * Main routine for reading/parsing the headers
287 * of a message content.
289 * toplevel = 1 # we are at the top level of the message
290 * toplevel = 0 # we are inside message type or multipart type
291 * # other than multipart/digest
292 * toplevel = -1 # we are inside multipart/digest
293 * NB: on failure we will fclose(in)!
297 get_content (FILE *in
, char *file
, int toplevel
)
300 char buf
[NMH_BUFSIZ
], name
[NAMESZ
];
304 m_getfld_state_t gstate
;
306 /* allocate the content structure */
309 ct
->c_file
= mh_xstrdup(FENDNULL(file
));
310 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
313 * Parse the header fields for this
314 * content into a linked list.
316 gstate
= m_getfld_state_init(in
);
317 m_getfld_track_filepos2(&gstate
);
318 for (compnum
= 1;;) {
319 int bufsz
= sizeof buf
;
320 switch (state
= m_getfld2(&gstate
, name
, buf
, &bufsz
)) {
325 /* get copies of the buffers */
326 np
= mh_xstrdup(name
);
327 vp
= mh_xstrdup(buf
);
329 /* if necessary, get rest of field */
330 while (state
== FLDPLUS
) {
332 state
= m_getfld2(&gstate
, name
, buf
, &bufsz
);
333 vp
= add (buf
, vp
); /* add to previous value */
336 /* Now add the header data to the list */
337 add_header (ct
, np
, vp
);
339 /* continue, to see if this isn't the last header field */
340 ct
->c_begin
= ftell (in
) + 1;
344 /* There are two cases. The unusual one is when there is no
345 * blank line between the headers and the body. This is
346 * indicated by the name of the header starting with `:'.
348 * For both cases, normal first, `1' is the desired c_begin
349 * file position for the start of the body, and `2' is the
350 * file position when buf is returned.
352 * f o o : b a r \n \n b o d y \n bufsz = 6
354 * f o o : b a r \n b o d y \n bufsz = 4
357 * For the normal case, bufsz includes the
358 * header-terminating `\n', even though it is not in buf,
359 * but bufsz isn't affected when it's missing in the unusual
361 if (name
[0] == ':') {
362 ct
->c_begin
= ftell(in
) - bufsz
;
364 ct
->c_begin
= ftell (in
) - (bufsz
- 1);
369 ct
->c_begin
= ftell (in
);
374 die("message format error in component #%d", compnum
);
377 die("getfld() returned %d", state
);
380 /* break out of the loop */
383 m_getfld_state_destroy (&gstate
);
386 * Read the content headers. We will parse the
387 * MIME related header fields into their various
388 * structures and set internal flags related to
389 * content type/subtype, etc.
392 hp
= ct
->c_first_hf
; /* start at first header field */
394 /* Get MIME-Version field */
395 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
400 vrsn
= mh_xstrdup(FENDNULL(hp
->value
));
402 /* Now, cleanup this field */
405 while (isspace ((unsigned char) *cp
))
407 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
409 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
410 if (!isspace ((unsigned char) *dp
))
414 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
417 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
420 for (dp
= cp
; istoken (*dp
); dp
++)
424 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
427 inform("message %s has unknown value for %s: field (%s), continuing...",
428 ct
->c_file
, VRSN_FIELD
, cp
);
433 if (! suppress_multiple_mime_version_warning
)
434 inform("message %s has multiple %s: fields",
435 ct
->c_file
, VRSN_FIELD
);
439 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
440 /* Get Content-Type field */
441 struct str2init
*s2i
;
442 CI ci
= &ct
->c_ctinfo
;
444 /* Check if we've already seen a Content-Type header */
446 inform("message %s has multiple %s: fields",
447 ct
->c_file
, TYPE_FIELD
);
451 /* Parse the Content-Type field */
452 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
456 * Set the Init function and the internal
457 * flag for this content type.
459 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
460 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
462 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
464 ct
->c_type
= s2i
->si_val
;
465 ct
->c_ctinitfnx
= s2i
->si_init
;
467 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
468 /* Get Content-Transfer-Encoding field */
470 struct str2init
*s2i
;
473 * Check if we've already seen the
474 * Content-Transfer-Encoding field
477 inform("message %s has multiple %s: fields",
478 ct
->c_file
, ENCODING_FIELD
);
482 /* get copy of this field */
483 ct
->c_celine
= cp
= mh_xstrdup(FENDNULL(hp
->value
));
485 while (isspace ((unsigned char) *cp
))
487 for (dp
= cp
; istoken (*dp
); dp
++)
493 * Find the internal flag and Init function
494 * for this transfer encoding.
496 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
497 if (!strcasecmp (cp
, s2i
->si_key
))
499 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
502 ct
->c_encoding
= s2i
->si_val
;
504 /* Call the Init function for this encoding */
505 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
508 else if (!strcasecmp (hp
->name
, ID_FIELD
)) {
509 /* Get Content-ID field */
510 ct
->c_id
= add (hp
->value
, ct
->c_id
);
512 else if (!strcasecmp (hp
->name
, DESCR_FIELD
)) {
513 /* Get Content-Description field */
514 ct
->c_descr
= add (hp
->value
, ct
->c_descr
);
516 else if (!strcasecmp (hp
->name
, DISPO_FIELD
)) {
517 /* Get Content-Disposition field */
518 if (get_dispo(hp
->value
, ct
, 0) == NOTOK
)
523 hp
= hp
->next
; /* next header field */
527 * Check if we saw a Content-Type field.
528 * If not, then assign a default value for
529 * it, and the Init function.
533 * If we are inside a multipart/digest message,
534 * so default type is message/rfc822
537 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
539 ct
->c_type
= CT_MESSAGE
;
540 ct
->c_ctinitfnx
= InitMessage
;
543 * Else default type is text/plain
545 if (get_ctinfo ("text/plain", ct
, 0) == NOTOK
)
547 ct
->c_type
= CT_TEXT
;
548 ct
->c_ctinitfnx
= InitText
;
552 /* Use default Transfer-Encoding, if necessary */
554 ct
->c_encoding
= CE_7BIT
;
567 * small routine to add header field to list
571 add_header (CT ct
, char *name
, char *value
)
575 /* allocate header field structure */
578 /* link data into header structure */
583 /* link header structure into the list */
584 if (ct
->c_first_hf
== NULL
) {
585 ct
->c_first_hf
= hp
; /* this is the first */
588 ct
->c_last_hf
->next
= hp
; /* add it to the end */
597 * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
598 * directives. Fills in the information of the CTinfo structure.
601 get_ctinfo (char *cp
, CT ct
, int magic
)
610 /* store copy of Content-Type line */
611 cp
= ct
->c_ctline
= mh_xstrdup(FENDNULL(cp
));
613 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
616 /* change newlines to spaces */
617 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
620 /* trim trailing spaces */
621 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
622 if (!isspace ((unsigned char) *dp
))
627 fprintf (stderr
, "%s: %s\n", TYPE_FIELD
, cp
);
629 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
630 &ci
->ci_comment
) == NOTOK
)
633 for (dp
= cp
; istoken (*dp
); dp
++)
637 ci
->ci_type
= mh_xstrdup(cp
); /* store content type */
642 inform("invalid %s: field in message %s (empty type)",
643 TYPE_FIELD
, ct
->c_file
);
646 to_lower(ci
->ci_type
);
648 while (isspace ((unsigned char) *cp
))
651 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
652 &ci
->ci_comment
) == NOTOK
)
657 ci
->ci_subtype
= mh_xstrdup("");
662 while (isspace ((unsigned char) *cp
))
665 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
666 &ci
->ci_comment
) == NOTOK
)
669 for (dp
= cp
; istoken (*dp
); dp
++)
673 ci
->ci_subtype
= mh_xstrdup(cp
); /* store the content subtype */
677 if (!*ci
->ci_subtype
) {
678 inform("invalid %s: field in message %s (empty subtype for \"%s\")",
679 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
682 to_lower(ci
->ci_subtype
);
685 while (isspace ((unsigned char) *cp
))
688 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
689 &ci
->ci_comment
) == NOTOK
)
692 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
693 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
694 &ci
->ci_comment
)) != OK
) {
695 return status
== NOTOK
? NOTOK
: OK
;
699 * Get any <Content-Id> given in buffer
701 if (magic
&& *cp
== '<') {
704 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
705 inform("invalid ID in message %s", ct
->c_file
);
711 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
717 while (isspace ((unsigned char) *cp
))
722 * Get any [Content-Description] given in buffer.
724 if (magic
&& *cp
== '[') {
726 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
730 inform("invalid description in message %s", ct
->c_file
);
738 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
744 while (isspace ((unsigned char) *cp
))
749 * Get any {Content-Disposition} given in buffer.
751 if (magic
&& *cp
== '{') {
753 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
757 inform("invalid disposition in message %s", ct
->c_file
);
765 if (get_dispo(cp
, ct
, 1) != OK
)
771 while (isspace ((unsigned char) *cp
))
776 * Get any extension directives (right now just the content transfer
777 * encoding, but maybe others) that we care about.
780 if (magic
&& *cp
== '*') {
782 * See if it's a CTE we match on
787 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
791 inform("invalid null transfer encoding specification");
798 ct
->c_reqencoding
= CE_UNKNOWN
;
800 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
801 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
802 ct
->c_reqencoding
= kv
->kv_value
;
807 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
808 inform("invalid CTE specification: \"%s\"", dp
);
812 while (isspace ((unsigned char) *cp
))
817 * Check if anything is left over
821 ci
->ci_magic
= mh_xstrdup(cp
);
823 /* If there is a Content-Disposition header and it doesn't
824 have a *filename=, extract it from the magic contents.
825 The r1bindex call skips any leading directory
827 if (ct
->c_dispo_type
&&
828 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
829 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
830 r1bindex(ci
->ci_magic
, '/'), 0);
834 inform("extraneous information in message %s's %s: field\n"
835 " (%s)", ct
->c_file
, TYPE_FIELD
, cp
);
843 * Parse out a Content-Disposition header. A lot of this is cribbed from
847 get_dispo (char *cp
, CT ct
, int buildflag
)
849 char *dp
, *dispoheader
;
854 * Save the whole copy of the Content-Disposition header, unless we're
855 * processing a mhbuild directive. A NULL c_dispo will be a flag to
856 * mhbuild that the disposition header needs to be generated at that
860 dispoheader
= cp
= mh_xstrdup(FENDNULL(cp
));
862 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
865 /* change newlines to spaces */
866 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
869 /* trim trailing spaces */
870 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
871 if (!isspace ((unsigned char) *dp
))
876 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
878 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
884 for (dp
= cp
; istoken (*dp
); dp
++)
888 ct
->c_dispo_type
= mh_xstrdup(cp
); /* store disposition type */
892 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
895 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
896 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
898 if (status
== NOTOK
) {
903 inform("extraneous information in message %s's %s: field\n (%s)",
904 ct
->c_file
, DISPO_FIELD
, cp
);
910 ct
->c_dispo
= dispoheader
;
917 get_comment (const char *filename
, const char *fieldname
, char **ap
,
922 char c
, buffer
[BUFSIZ
], *dp
;
932 inform("invalid comment in message %s's %s: field",
933 filename
, fieldname
);
938 if ((c
= *cp
++) == '\0')
961 if ((dp
= *commentp
)) {
962 *commentp
= concat (dp
, " ", buffer
, NULL
);
965 *commentp
= mh_xstrdup(buffer
);
969 while (isspace ((unsigned char) *cp
))
980 * Handles content types audio, image, and video.
981 * There's not much to do right here.
989 return OK
; /* not much to do here */
1000 char buffer
[BUFSIZ
];
1005 CI ci
= &ct
->c_ctinfo
;
1007 /* check for missing subtype */
1008 if (!*ci
->ci_subtype
)
1009 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1012 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1014 /* allocate text character set structure */
1016 ct
->c_ctparams
= (void *) t
;
1018 /* scan for charset parameter */
1019 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1020 if (!strcasecmp (pm
->pm_name
, "charset"))
1023 /* check if content specified a character set */
1025 chset
= pm
->pm_value
;
1026 t
->tx_charset
= CHARSET_SPECIFIED
;
1028 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1032 * If we can not handle character set natively,
1033 * then check profile for string to modify the
1034 * terminal or display method.
1036 * termproc is for mhshow, though mhlist -debug prints it, too.
1038 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1039 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1040 if ((cp
= context_find (buffer
)))
1041 ct
->c_termproc
= mh_xstrdup(cp
);
1053 InitMultiPart (CT ct
)
1063 struct multipart
*m
;
1064 struct part
*part
, **next
;
1065 CI ci
= &ct
->c_ctinfo
;
1070 * The encoding for multipart messages must be either
1071 * 7bit, 8bit, or binary (per RFC 2045).
1073 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1074 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1075 /* Copy the Content-Transfer-Encoding header field body so we can
1076 remove any trailing whitespace and leading blanks from it. */
1077 char *cte
= mh_xstrdup(ct
->c_celine
? ct
->c_celine
: "(null)");
1079 bp
= cte
+ strlen (cte
) - 1;
1080 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1081 for (bp
= cte
; isblank((unsigned char)*bp
); ++bp
) continue;
1083 inform("\"%s/%s\" type in message %s must be encoded in\n"
1084 "7bit, 8bit, or binary, per RFC 2045 (6.4). "
1085 "mhfixmsg -fixcte can fix it, or\n"
1086 "manually edit the file and change the \"%s\"\n"
1087 "Content-Transfer-Encoding to one of those. For now, continuing...",
1088 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1095 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1098 * Check for "boundary" parameter, which is
1099 * required for multipart messages.
1102 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1103 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1109 /* complain if boundary parameter is missing */
1111 inform("a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1112 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1116 /* allocate primary structure for multipart info */
1118 ct
->c_ctparams
= (void *) m
;
1120 /* check if boundary parameter contains only whitespace characters */
1121 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1124 inform("invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1125 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1129 /* remove trailing whitespace from boundary parameter */
1130 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1131 if (!isspace ((unsigned char) *dp
))
1135 /* record boundary separators */
1136 m
->mp_start
= concat (bp
, "\n", NULL
);
1137 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1139 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1140 advise (ct
->c_file
, "unable to open for reading");
1144 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1146 next
= &m
->mp_parts
;
1150 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1155 if (bufp
[0] != '-' || bufp
[1] != '-')
1158 if (strcmp (bufp
+ 2, m
->mp_start
))
1163 next
= &part
->mp_next
;
1165 if (!(p
= get_content (fp
, ct
->c_file
,
1166 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1174 fseek (fp
, pos
, SEEK_SET
);
1177 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1181 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1182 if (p
->c_end
< p
->c_begin
)
1183 p
->c_begin
= p
->c_end
;
1188 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1193 if (! suppress_bogus_mp_content_warning
) {
1194 inform("bogus multipart content in message %s", ct
->c_file
);
1196 bogus_mp_content
= true;
1198 if (!inout
&& part
) {
1200 p
->c_end
= ct
->c_end
;
1202 if (p
->c_begin
>= p
->c_end
) {
1203 for (next
= &m
->mp_parts
; *next
!= part
;
1204 next
= &((*next
)->mp_next
))
1213 /* reverse the order of the parts for multipart/alternative */
1214 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1220 * label all subparts with part number, and
1221 * then initialize the content of the subpart.
1226 char partnam
[BUFSIZ
];
1229 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1230 pp
= partnam
+ strlen (partnam
);
1235 for (part
= m
->mp_parts
, partnum
= 1; part
;
1236 part
= part
->mp_next
, partnum
++) {
1239 sprintf (pp
, "%d", partnum
);
1240 p
->c_partno
= mh_xstrdup(partnam
);
1242 /* initialize the content of the subparts */
1243 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1252 get_leftover_mp_content (ct
, 1);
1253 get_leftover_mp_content (ct
, 0);
1263 * reverse the order of the parts of a multipart/alternative,
1264 * presumably to put the "most favored" alternative first, for
1265 * ease of choosing/displaying it later on. from a mail message on
1266 * nmh-workers, from kenh:
1267 * "Stock" MH 6.8.5 did not have a reverse_parts() function, but I
1268 * see code in mhn that did the same thing... According to the RCS
1269 * logs, that code was around from the initial checkin of mhn.c by
1270 * John Romine in 1992, which is as far back as we have."
1273 reverse_parts (CT ct
)
1275 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1279 /* Reverse the order of its parts by walking the mp_parts list
1280 and pushing each node to the front. */
1281 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1282 next
= part
->mp_next
;
1283 part
->mp_next
= m
->mp_parts
;
1289 move_preferred_part(CT ct
, mime_type_subtype
*pref
)
1291 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1292 struct part
*part
, *prev
, *head
, *nhead
, *ntail
;
1296 /* move the matching part(s) to the head of the list: walk the
1297 * list of parts, move matching parts to a new list (maintaining
1298 * their order), and finally, concatenate the old list onto the
1305 head
->mp_next
= m
->mp_parts
;
1306 nhead
->mp_next
= NULL
;
1310 part
= head
->mp_next
;
1311 while (part
!= NULL
) {
1312 ci
= &part
->mp_part
->c_ctinfo
;
1313 if (!strcasecmp(ci
->ci_type
, pref
->type
) &&
1315 !strcasecmp(ci
->ci_subtype
, pref
->subtype
))) {
1316 prev
->mp_next
= part
->mp_next
;
1317 part
->mp_next
= NULL
;
1318 ntail
->mp_next
= part
;
1320 part
= prev
->mp_next
;
1323 part
= prev
->mp_next
;
1326 ntail
->mp_next
= head
->mp_next
;
1327 m
->mp_parts
= nhead
->mp_next
;
1331 * move parts that match the user's preferences (-prefer) to the head
1332 * of the line. process preferences in reverse so first one given
1333 * ends up first in line
1339 for (i
= 0; i
< npreferred
; i
++)
1340 move_preferred_part(ct
, mime_preference
+ i
);
1345 /* parse_mime() arranges alternates in reverse (priority) order. This
1346 function can be used to reverse them back. This will put, for
1347 example, a text/plain part before a text/html part in a
1348 multipart/alternative part, for example, where it belongs. */
1350 reverse_alternative_parts (CT ct
)
1352 if (ct
->c_type
== CT_MULTIPART
) {
1353 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1356 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1360 /* And call recursively on each part of a multipart. */
1361 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1362 reverse_alternative_parts (part
->mp_part
);
1375 CI ci
= &ct
->c_ctinfo
;
1377 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1378 inform("\"%s/%s\" type in message %s should be encoded in "
1379 "7bit or 8bit, continuing...", ci
->ci_type
, ci
->ci_subtype
,
1384 /* check for missing subtype */
1385 if (!*ci
->ci_subtype
)
1386 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1389 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1391 switch (ct
->c_subtype
) {
1392 case MESSAGE_RFC822
:
1395 case MESSAGE_PARTIAL
:
1401 ct
->c_ctparams
= (void *) p
;
1403 /* scan for parameters "id", "number", and "total" */
1404 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1405 if (!strcasecmp (pm
->pm_name
, "id")) {
1406 p
->pm_partid
= mh_xstrdup(FENDNULL(pm
->pm_value
));
1409 if (!strcasecmp (pm
->pm_name
, "number")) {
1410 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1411 || p
->pm_partno
< 1) {
1413 inform("invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1414 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1415 ct
->c_file
, TYPE_FIELD
);
1420 if (!strcasecmp (pm
->pm_name
, "total")) {
1421 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1430 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1431 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1432 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1438 case MESSAGE_EXTERNAL
:
1446 ct
->c_ctparams
= (void *) e
;
1449 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1450 advise (ct
->c_file
, "unable to open for reading");
1454 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1456 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1464 p
->c_ceopenfnx
= NULL
;
1465 if ((exresult
= params_external (ct
, 0)) != NOTOK
1466 && p
->c_ceopenfnx
== openMail
) {
1470 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1472 content_error (NULL
, ct
,
1473 "empty body for access-type=mail-server");
1477 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1478 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1480 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1482 adios ("failed", "fread");
1485 die("unexpected EOF from fread");
1488 bp
+= cc
, size
-= cc
;
1495 p
->c_end
= p
->c_begin
;
1500 if (exresult
== NOTOK
)
1502 if (e
->eb_flags
== NOTOK
)
1505 switch (p
->c_type
) {
1510 if (p
->c_subtype
!= MESSAGE_RFC822
)
1514 e
->eb_partno
= ct
->c_partno
;
1516 (*p
->c_ctinitfnx
) (p
);
1531 params_external (CT ct
, int composing
)
1534 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1535 CI ci
= &ct
->c_ctinfo
;
1537 ct
->c_ceopenfnx
= NULL
;
1538 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1539 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1540 struct str2init
*s2i
;
1541 CT p
= e
->eb_content
;
1543 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1544 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1547 e
->eb_access
= pm
->pm_value
;
1548 e
->eb_flags
= NOTOK
;
1549 p
->c_encoding
= CE_EXTERNAL
;
1552 e
->eb_access
= s2i
->si_key
;
1553 e
->eb_flags
= s2i
->si_val
;
1554 p
->c_encoding
= CE_EXTERNAL
;
1556 /* Call the Init function for this external type */
1557 if ((*s2i
->si_init
)(p
) == NOTOK
)
1561 if (!strcasecmp (pm
->pm_name
, "name")) {
1562 e
->eb_name
= pm
->pm_value
;
1565 if (!strcasecmp (pm
->pm_name
, "permission")) {
1566 e
->eb_permission
= pm
->pm_value
;
1569 if (!strcasecmp (pm
->pm_name
, "site")) {
1570 e
->eb_site
= pm
->pm_value
;
1573 if (!strcasecmp (pm
->pm_name
, "directory")) {
1574 e
->eb_dir
= pm
->pm_value
;
1577 if (!strcasecmp (pm
->pm_name
, "mode")) {
1578 e
->eb_mode
= pm
->pm_value
;
1581 if (!strcasecmp (pm
->pm_name
, "size")) {
1582 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1585 if (!strcasecmp (pm
->pm_name
, "server")) {
1586 e
->eb_server
= pm
->pm_value
;
1589 if (!strcasecmp (pm
->pm_name
, "subject")) {
1590 e
->eb_subject
= pm
->pm_value
;
1593 if (!strcasecmp (pm
->pm_name
, "url")) {
1595 * According to RFC 2017, we have to remove all whitespace from
1599 char *u
, *p
= pm
->pm_value
;
1600 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1602 for (; *p
!= '\0'; p
++) {
1603 if (! isspace((unsigned char) *p
))
1610 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1611 e
->eb_body
= getcpy (pm
->pm_value
);
1616 if (!e
->eb_access
) {
1617 inform("invalid parameters for \"%s/%s\" type in message %s's %s field",
1618 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1631 InitApplication (CT ct
)
1633 CI ci
= &ct
->c_ctinfo
;
1636 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1643 * TRANSFER ENCODINGS
1647 init_encoding (CT ct
, OpenCEFunc openfnx
)
1649 ct
->c_ceopenfnx
= openfnx
;
1650 ct
->c_ceclosefnx
= close_encoding
;
1651 ct
->c_cesizefnx
= size_encoding
;
1658 close_encoding (CT ct
)
1660 CE ce
= &ct
->c_cefile
;
1669 static unsigned long
1670 size_encoding (CT ct
)
1675 CE ce
= &ct
->c_cefile
;
1678 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1679 return (long) st
.st_size
;
1682 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1683 return (long) st
.st_size
;
1687 if (ct
->c_encoding
== CE_EXTERNAL
)
1688 return ct
->c_end
- ct
->c_begin
;
1691 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1692 return ct
->c_end
- ct
->c_begin
;
1694 if (fstat (fd
, &st
) != NOTOK
)
1695 size
= (long) st
.st_size
;
1699 (*ct
->c_ceclosefnx
) (ct
);
1711 return init_encoding (ct
, openBase64
);
1716 openBase64 (CT ct
, char **file
)
1720 bool own_ct_fp
= false;
1721 char *cp
, *buffer
= NULL
;
1722 /* sbeck -- handle suffixes */
1724 CE ce
= &ct
->c_cefile
;
1725 unsigned char *decoded
;
1729 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1734 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1735 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1741 if (*file
== NULL
) {
1744 ce
->ce_file
= mh_xstrdup(*file
);
1748 /* sbeck@cise.ufl.edu -- handle suffixes */
1750 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1751 if (ce
->ce_unlink
) {
1752 /* Create temporary file with filename extension. */
1753 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1754 die("unable to create temporary file in %s",
1758 ce
->ce_file
= add (cp
, ce
->ce_file
);
1760 } else if (*file
== NULL
) {
1762 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1763 die("unable to create temporary file in %s",
1766 ce
->ce_file
= mh_xstrdup(tempfile
);
1769 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1770 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1774 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1775 die("internal error(1)");
1777 buffer
= mh_xmalloc (len
+ 1);
1780 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1781 content_error (ct
->c_file
, ct
, "unable to open for reading");
1787 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1790 switch (cc
= read (fd
, cp
, len
)) {
1792 content_error (ct
->c_file
, ct
, "error reading from");
1796 content_error (NULL
, ct
, "premature eof");
1807 /* decodeBase64() requires null-terminated input. */
1810 if (decodeBase64 (buffer
, &decoded
, &decoded_len
,
1811 ct
->c_type
== CT_TEXT
) != OK
)
1816 unsigned char *decoded_p
= decoded
;
1817 for (i
= 0; i
< decoded_len
; ++i
) {
1818 putc (*decoded_p
++, ce
->ce_fp
);
1821 if (ferror (ce
->ce_fp
)) {
1822 content_error (ce
->ce_file
, ct
, "error writing to");
1827 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1829 if (fflush (ce
->ce_fp
)) {
1830 content_error (ce
->ce_file
, ct
, "error writing to");
1834 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1837 *file
= ce
->ce_file
;
1843 return fileno (ce
->ce_fp
);
1850 free_encoding (ct
, 0);
1860 static char hex2nib
[0x80] = {
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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1867 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1868 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1869 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1870 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1871 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1872 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1873 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1874 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1875 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1876 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1883 return init_encoding (ct
, openQuoted
);
1888 openQuoted (CT ct
, char **file
)
1890 int cc
, len
, quoted
;
1891 bool own_ct_fp
= false;
1897 CE ce
= &ct
->c_cefile
;
1898 /* sbeck -- handle suffixes */
1902 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1907 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1908 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1914 if (*file
== NULL
) {
1917 ce
->ce_file
= mh_xstrdup(*file
);
1921 /* sbeck@cise.ufl.edu -- handle suffixes */
1923 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1924 if (ce
->ce_unlink
) {
1925 /* Create temporary file with filename extension. */
1926 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1927 die("unable to create temporary file in %s",
1931 ce
->ce_file
= add (cp
, ce
->ce_file
);
1933 } else if (*file
== NULL
) {
1935 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1936 die("unable to create temporary file in %s",
1939 ce
->ce_file
= mh_xstrdup(tempfile
);
1942 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1943 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1947 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1948 die("internal error(2)");
1951 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1952 content_error (ct
->c_file
, ct
, "unable to open for reading");
1960 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1962 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
1963 content_error (NULL
, ct
, "premature eof");
1967 if ((cc
= gotlen
) > len
)
1971 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
1972 if (!isspace ((unsigned char) *ep
))
1977 for (; cp
< ep
; cp
++) {
1979 /* in an escape sequence */
1981 /* at byte 1 of an escape sequence */
1982 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
1983 /* next is byte 2 */
1986 /* at byte 2 of an escape sequence */
1988 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
1989 putc (mask
, ce
->ce_fp
);
1990 if (ferror (ce
->ce_fp
)) {
1991 content_error (ce
->ce_file
, ct
, "error writing to");
1994 /* finished escape sequence; next may be literal or a new
1995 * escape sequence */
1998 /* on to next byte */
2002 /* not in an escape sequence */
2004 /* starting an escape sequence, or invalid '='? */
2005 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2006 /* "=\n" soft line break, eat the \n */
2010 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2011 /* We don't have 2 bytes left, so this is an invalid
2012 * escape sequence; just show the raw bytes (below). */
2013 } else if (isxdigit ((unsigned char) cp
[1]) &&
2014 isxdigit ((unsigned char) cp
[2])) {
2015 /* Next 2 bytes are hex digits, making this a valid escape
2016 * sequence; let's decode it (above). */
2020 /* One or both of the next 2 is out of range, making this
2021 * an invalid escape sequence; just show the raw bytes
2025 /* Just show the raw byte. */
2026 putc (*cp
, ce
->ce_fp
);
2027 if (ferror (ce
->ce_fp
)) {
2028 content_error (ce
->ce_file
, ct
, "error writing to");
2034 content_error (NULL
, ct
,
2035 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2039 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2041 if (fflush (ce
->ce_fp
)) {
2042 content_error (ce
->ce_file
, ct
, "error writing to");
2046 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2049 *file
= ce
->ce_file
;
2055 return fileno (ce
->ce_fp
);
2058 free_encoding (ct
, 0);
2075 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2078 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2084 open7Bit (CT ct
, char **file
)
2087 bool own_ct_fp
= false;
2088 char buffer
[BUFSIZ
];
2089 /* sbeck -- handle suffixes */
2092 CE ce
= &ct
->c_cefile
;
2095 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2100 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2101 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2107 if (*file
== NULL
) {
2110 ce
->ce_file
= mh_xstrdup(*file
);
2114 /* sbeck@cise.ufl.edu -- handle suffixes */
2116 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2117 if (ce
->ce_unlink
) {
2118 /* Create temporary file with filename extension. */
2119 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2120 die("unable to create temporary file in %s",
2124 ce
->ce_file
= add (cp
, ce
->ce_file
);
2126 } else if (*file
== NULL
) {
2128 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2129 die("unable to create temporary file in %s",
2132 ce
->ce_file
= mh_xstrdup(tempfile
);
2135 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2136 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2140 if (ct
->c_type
== CT_MULTIPART
) {
2141 CI ci
= &ct
->c_ctinfo
;
2145 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2146 len
+= LEN(TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2147 + 1 + strlen (ci
->ci_subtype
);
2148 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2151 fputs (buffer
, ce
->ce_fp
);
2155 if (ci
->ci_comment
) {
2156 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2157 fputs ("\n\t", ce
->ce_fp
);
2161 putc (' ', ce
->ce_fp
);
2164 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2167 fprintf (ce
->ce_fp
, "\n");
2169 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2171 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2173 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2174 fprintf (ce
->ce_fp
, "\n");
2177 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2178 die("internal error(3)");
2181 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2182 content_error (ct
->c_file
, ct
, "unable to open for reading");
2188 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2190 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2192 content_error (ct
->c_file
, ct
, "error reading from");
2196 content_error (NULL
, ct
, "premature eof");
2204 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2205 advise ("open7Bit", "fwrite");
2207 if (ferror (ce
->ce_fp
)) {
2208 content_error (ce
->ce_file
, ct
, "error writing to");
2213 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2215 if (fflush (ce
->ce_fp
)) {
2216 content_error (ce
->ce_file
, ct
, "error writing to");
2220 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2223 *file
= ce
->ce_file
;
2228 return fileno (ce
->ce_fp
);
2231 free_encoding (ct
, 0);
2245 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2247 char cachefile
[BUFSIZ
];
2250 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2255 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2256 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2262 if (find_cache(ct
, rcachesw
, NULL
, cb
->c_id
,
2263 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2264 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2265 ce
->ce_file
= mh_xstrdup(cachefile
);
2269 admonish (cachefile
, "unable to fopen for reading");
2272 *fd
= ce
->ce_fp
? fileno (ce
->ce_fp
) : -1;
2276 *file
= ce
->ce_file
;
2277 *fd
= fileno (ce
->ce_fp
);
2288 return init_encoding (ct
, openFile
);
2293 openFile (CT ct
, char **file
)
2296 char cachefile
[BUFSIZ
];
2297 struct exbody
*e
= ct
->c_ctexbody
;
2298 CE ce
= &ct
->c_cefile
;
2300 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2312 content_error (NULL
, ct
, "missing name parameter");
2316 ce
->ce_file
= mh_xstrdup(e
->eb_name
);
2319 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2320 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2324 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2325 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2326 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2330 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2331 if ((fp
= fopen (cachefile
, "w"))) {
2333 char buffer
[BUFSIZ
];
2334 FILE *gp
= ce
->ce_fp
;
2336 fseek (gp
, 0L, SEEK_SET
);
2338 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2340 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2341 advise ("openFile", "fwrite");
2346 admonish (ce
->ce_file
, "error reading");
2347 (void) m_unlink (cachefile
);
2348 } else if (ferror (fp
)) {
2349 admonish (cachefile
, "error writing");
2350 (void) m_unlink (cachefile
);
2357 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2358 *file
= ce
->ce_file
;
2359 return fileno (ce
->ce_fp
);
2369 return init_encoding (ct
, openFTP
);
2374 openFTP (CT ct
, char **file
)
2380 char *bp
, *ftp
, *user
, *pass
;
2381 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2383 CE ce
= &ct
->c_cefile
;
2384 static char *username
= NULL
;
2385 static char *password
= NULL
;
2389 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2395 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2406 if (!e
->eb_name
|| !e
->eb_site
) {
2407 content_error (NULL
, ct
, "missing %s parameter",
2408 e
->eb_name
? "site": "name");
2412 /* Get the buffer ready to go */
2414 buflen
= sizeof(buffer
);
2417 * Construct the query message for user
2419 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2425 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2431 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2432 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2437 if (e
->eb_size
> 0) {
2438 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2443 snprintf (bp
, buflen
, "? ");
2446 * Now, check the answer
2448 if (!read_yes_or_no_if_tty (buffer
))
2453 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2457 ruserpass (e
->eb_site
, &username
, &password
, 0);
2462 ce
->ce_unlink
= (*file
== NULL
);
2464 cachefile
[0] = '\0';
2465 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2466 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2467 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2468 if (*file
== NULL
) {
2475 ce
->ce_file
= mh_xstrdup(*file
);
2477 ce
->ce_file
= mh_xstrdup(cachefile
);
2480 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2481 die("unable to create temporary file in %s",
2484 ce
->ce_file
= mh_xstrdup(tempfile
);
2487 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2488 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2497 vec
[vecp
++] = r1bindex (ftp
, '/');
2498 vec
[vecp
++] = e
->eb_site
;
2501 vec
[vecp
++] = e
->eb_dir
;
2502 vec
[vecp
++] = e
->eb_name
;
2503 vec
[vecp
++] = ce
->ce_file
,
2504 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2505 ? "ascii" : "binary";
2513 adios ("fork", "unable to");
2517 close (fileno (ce
->ce_fp
));
2519 fprintf (stderr
, "unable to exec ");
2525 if (pidXwait (child_id
, NULL
)) {
2526 username
= password
= NULL
;
2536 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2541 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2542 if ((fp
= fopen (cachefile
, "w"))) {
2544 FILE *gp
= ce
->ce_fp
;
2546 fseek (gp
, 0L, SEEK_SET
);
2548 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2550 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2551 advise ("openFTP", "fwrite");
2556 admonish (ce
->ce_file
, "error reading");
2557 (void) m_unlink (cachefile
);
2558 } else if (ferror (fp
)) {
2559 admonish (cachefile
, "error writing");
2560 (void) m_unlink (cachefile
);
2568 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2569 *file
= ce
->ce_file
;
2570 return fileno (ce
->ce_fp
);
2581 return init_encoding (ct
, openMail
);
2586 openMail (CT ct
, char **file
)
2588 int child_id
, fd
, vecp
;
2590 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2591 struct exbody
*e
= ct
->c_ctexbody
;
2592 CE ce
= &ct
->c_cefile
;
2594 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2605 if (!e
->eb_server
) {
2606 content_error (NULL
, ct
, "missing server parameter");
2610 /* Get buffer ready to go */
2612 buflen
= sizeof(buffer
);
2614 /* Now, construct query message */
2615 snprintf (bp
, buflen
, "Retrieve content");
2621 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2627 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2629 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2631 /* Now, check answer */
2632 if (!read_yes_or_no_if_tty (buffer
))
2636 vec
[vecp
++] = r1bindex (mailproc
, '/');
2637 vec
[vecp
++] = e
->eb_server
;
2638 vec
[vecp
++] = "-subject";
2639 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2640 vec
[vecp
++] = "-body";
2641 vec
[vecp
++] = e
->eb_body
;
2647 advise ("fork", "unable to");
2651 execvp (mailproc
, vec
);
2652 fprintf (stderr
, "unable to exec ");
2658 if (pidXwait (child_id
, NULL
) == OK
)
2659 inform("request sent");
2663 if (*file
== NULL
) {
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 ce
->ce_file
= mh_xstrdup(*file
);
2676 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2677 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2681 /* showproc is for mhshow and mhstore, though mhlist -debug
2682 * prints it, too. */
2683 free(ct
->c_showproc
);
2684 ct
->c_showproc
= mh_xstrdup("true");
2686 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2687 *file
= ce
->ce_file
;
2688 return fileno (ce
->ce_fp
);
2699 return init_encoding (ct
, openURL
);
2704 openURL (CT ct
, char **file
)
2706 struct exbody
*e
= ct
->c_ctexbody
;
2707 CE ce
= &ct
->c_cefile
;
2708 char *urlprog
, *program
;
2709 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2713 struct msgs_array args
= { 0, 0, NULL
};
2716 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2720 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2724 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2736 content_error(NULL
, ct
, "missing url parameter");
2740 ce
->ce_unlink
= (*file
== NULL
);
2742 cachefile
[0] = '\0';
2744 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2745 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2746 if (*file
== NULL
) {
2753 ce
->ce_file
= mh_xstrdup(*file
);
2755 ce
->ce_file
= mh_xstrdup(cachefile
);
2758 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2759 die("unable to create temporary file in %s",
2762 ce
->ce_file
= mh_xstrdup(tempfile
);
2765 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2766 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2770 switch (child_id
= fork()) {
2772 adios ("fork", "unable to");
2776 argsplit_msgarg(&args
, urlprog
, &program
);
2777 app_msgarg(&args
, e
->eb_url
);
2778 app_msgarg(&args
, NULL
);
2779 dup2(fileno(ce
->ce_fp
), 1);
2780 close(fileno(ce
->ce_fp
));
2781 execvp(program
, args
.msgs
);
2782 fprintf(stderr
, "Unable to exec ");
2788 if (pidXwait(child_id
, NULL
)) {
2796 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2801 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2802 if ((fp
= fopen(cachefile
, "w"))) {
2804 FILE *gp
= ce
->ce_fp
;
2806 fseeko(gp
, 0, SEEK_SET
);
2808 while ((cc
= fread(buffer
, sizeof(*buffer
),
2809 sizeof(buffer
), gp
)) > 0)
2810 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2811 advise ("openURL", "fwrite");
2817 admonish(ce
->ce_file
, "error reading");
2818 (void) m_unlink (cachefile
);
2825 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2826 *file
= ce
->ce_file
;
2827 return fileno(ce
->ce_fp
);
2831 /* Multipart parts might have content before the first subpart and/or
2832 after the last subpart that hasn't been stored anywhere else, so do
2835 get_leftover_mp_content (CT ct
, int before
/* or after */)
2837 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2839 bool found_boundary
= false;
2845 char *content
= NULL
;
2847 if (! m
) return NOTOK
;
2850 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2852 /* Isolate the beginning of this part to the beginning of the
2853 first subpart and save any content between them. */
2854 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2855 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2856 boundary
= concat ("--", m
->mp_start
, NULL
);
2858 struct part
*last_subpart
= NULL
;
2859 struct part
*subpart
;
2861 /* Go to the last subpart to get its end position. */
2862 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
2863 last_subpart
= subpart
;
2866 if (last_subpart
== NULL
) return NOTOK
;
2868 /* Isolate the end of the last subpart to the end of this part
2869 and save any content between them. */
2870 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
2871 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
2872 boundary
= concat ("--", m
->mp_stop
, NULL
);
2875 /* Back up by 1 to pick up the newline. */
2876 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
2878 /* Don't look beyond beginning of first subpart (before) or
2879 next part (after). */
2880 if (read
> max
) bufp
[read
-max
] = '\0';
2883 if (! strcmp (bufp
, boundary
)) {
2884 found_boundary
= true;
2887 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
2888 found_boundary
= true;
2893 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
2895 char *old_content
= content
;
2896 content
= concat (content
, bufp
, NULL
);
2900 ? concat ("\n", bufp
, NULL
)
2901 : concat (bufp
, NULL
);
2906 if (found_boundary
|| read
> max
) break;
2908 if (read
> max
) break;
2912 /* Skip the newline if that's all there is. */
2916 /* Remove trailing newline, except at EOF. */
2917 if ((before
|| ! feof (ct
->c_fp
)) &&
2918 (cp
= content
+ strlen (content
)) > content
&&
2923 if (strlen (content
) > 1) {
2925 m
->mp_content_before
= content
;
2927 m
->mp_content_after
= content
;
2942 ct_type_str (int type
)
2945 case CT_APPLICATION
:
2946 return "application";
2962 return "unknown_type";
2968 ct_subtype_str (int type
, int subtype
)
2971 case CT_APPLICATION
:
2973 case APPLICATION_OCTETS
:
2975 case APPLICATION_POSTSCRIPT
:
2976 return "postscript";
2978 return "unknown_app_subtype";
2982 case MESSAGE_RFC822
:
2984 case MESSAGE_PARTIAL
:
2986 case MESSAGE_EXTERNAL
:
2989 return "unknown_msg_subtype";
2995 case MULTI_ALTERNATE
:
2996 return "alternative";
2999 case MULTI_PARALLEL
:
3004 return "unknown_multipart_subtype";
3015 return "unknown_text_subtype";
3018 return "unknown_type";
3024 ct_str_type (const char *type
)
3026 struct str2init
*s2i
;
3028 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3029 if (! strcasecmp (type
, s2i
->si_key
)) {
3033 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3042 ct_str_subtype (int type
, const char *subtype
)
3047 case CT_APPLICATION
:
3048 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3049 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3053 return kv
->kv_value
;
3055 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3056 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3060 return kv
->kv_value
;
3062 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3063 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3067 return kv
->kv_value
;
3069 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3070 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3074 return kv
->kv_value
;
3081 /* Find the content type and InitFunc for the CT. */
3082 const struct str2init
*
3083 get_ct_init (int type
)
3085 const struct str2init
*sp
;
3087 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3088 if (type
== sp
->si_val
) {
3097 ce_str (int encoding
)
3103 return "quoted-printable";
3119 /* Find the content type and InitFunc for the content encoding method. */
3120 const struct str2init
*
3121 get_ce_method (const char *method
)
3123 struct str2init
*sp
;
3125 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3126 if (! strcasecmp (method
, sp
->si_key
)) {
3135 * Parse a series of MIME attributes (or parameters) given a header as
3138 * Arguments include:
3140 * filename - Name of input file (for error messages)
3141 * fieldname - Name of field being processed
3142 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3143 * Updated to point to end of attributes when finished.
3144 * param_head - Pointer to head of parameter list
3145 * param_tail - Pointer to tail of parameter list
3146 * commentp - Pointer to header comment pointer (may be NULL)
3148 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3149 * DONE to indicate a benign error (minor parsing error, but the program
3154 parse_header_attrs (const char *filename
, const char *fieldname
,
3155 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3158 char *cp
= *header_attrp
;
3164 struct sectlist
*next
;
3170 struct sectlist
*sechead
;
3171 struct parmlist
*next
;
3172 } *pp
, *pp2
, *phead
= NULL
;
3174 while (*cp
== ';') {
3175 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3176 bool encoded
= false;
3177 bool partial
= false;
3178 int len
= 0, index
= 0;
3181 while (isspace ((unsigned char) *cp
))
3185 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3190 if (! suppress_extraneous_trailing_semicolon_warning
) {
3191 inform("extraneous trailing ';' in message %s's %s: "
3192 "parameter list", filename
, fieldname
);
3197 /* down case the attribute name */
3198 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3199 *dp
= tolower ((unsigned char) *dp
);
3201 for (up
= dp
; isspace ((unsigned char) *dp
);)
3203 if (dp
== cp
|| *dp
!= '=') {
3204 inform("invalid parameter in message %s's %s: field\n"
3205 " parameter %s (error detected at offset %ld)",
3206 filename
, fieldname
, cp
, (long)(dp
- cp
));
3211 * To handle RFC 2231, we have to deal with the following extensions:
3213 * name*=encoded-value
3214 * name*<N>=part-N-of-a-parameter-value
3215 * name*<N>*=encoded-part-N-of-a-parameter-value
3218 * If there's a * right before the equal sign, it's encoded.
3219 * If there's a * and one or more digits, then it's section N.
3221 * Remember we can have one or the other, or both. cp points to
3222 * beginning of name, up points past the last character in the
3226 for (vp
= cp
; vp
< up
; vp
++) {
3227 if (*vp
== '*' && vp
< up
- 1) {
3231 if (*vp
== '*' && vp
== up
- 1) {
3233 } else if (partial
) {
3234 if (isdigit((unsigned char) *vp
))
3235 index
= *vp
- '0' + index
* 10;
3237 inform("invalid parameter index in message %s's %s: field"
3238 "\n (parameter %s)", filename
, fieldname
, cp
);
3247 * Break out the parameter name and value sections and allocate
3251 nameptr
= mh_xmalloc(len
+ 1);
3252 strncpy(nameptr
, cp
, len
);
3253 nameptr
[len
] = '\0';
3255 for (dp
++; isspace ((unsigned char) *dp
);)
3260 * Single quotes delimit the character set and language tag.
3261 * They are required on the first section (or a complete
3266 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3272 charset
= mh_xmalloc(len
+ 1);
3273 strncpy(charset
, dp
, len
);
3274 charset
[len
] = '\0';
3280 inform("missing charset in message %s's %s: field\n"
3281 " (parameter %s)", filename
, fieldname
, nameptr
);
3287 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3294 lang
= mh_xmalloc(len
+ 1);
3295 strncpy(lang
, dp
, len
);
3302 inform("missing language tag in message %s's %s: field\n"
3303 " (parameter %s)", filename
, fieldname
, nameptr
);
3313 * At this point vp should be pointing at the beginning
3314 * of the encoded value/section. Continue until we reach
3315 * the end or get whitespace. But first, calculate the
3316 * length so we can allocate the correct buffer size.
3319 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3321 if (*(vp
+ 1) == '\0' ||
3322 !isxdigit((unsigned char) *(vp
+ 1)) ||
3323 *(vp
+ 2) == '\0' ||
3324 !isxdigit((unsigned char) *(vp
+ 2))) {
3325 inform("invalid encoded sequence in message %s's %s: field\n"
3326 " (parameter %s)", filename
, fieldname
, nameptr
);
3337 up
= valptr
= mh_xmalloc(len
+ 1);
3339 for (vp
= dp
; istoken(*vp
); vp
++) {
3341 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3352 * A "normal" string. If it's got a leading quote, then we
3353 * strip the quotes out. Otherwise go until we reach the end
3354 * or get whitespace. Note we scan it twice; once to get the
3355 * length, then the second time copies it into the destination
3362 for (cp
= dp
+ 1;;) {
3366 inform("invalid quoted-string in message %s's %s: field\n"
3367 " (parameter %s)", filename
, fieldname
, nameptr
);
3387 for (cp
= dp
; istoken (*cp
); cp
++) {
3392 valptr
= mh_xmalloc(len
+ 1);
3396 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3404 strncpy(valptr
, cp
= dp
, len
);
3412 * If 'partial' is set, we don't allocate a parameter now. We
3413 * put it on the parameter linked list to be reassembled later.
3415 * "phead" points to a list of all parameters we need to reassemble.
3416 * Each parameter has a list of sections. We insert the sections in
3421 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3422 if (strcasecmp(nameptr
, pp
->name
) == 0) {
3437 * Insert this into the section linked list
3445 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3446 sp
->next
= pp
->sechead
;
3449 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3450 if (sp2
->index
== sp
->index
) {
3451 inform("duplicate index (%d) in message %s's %s: field"
3452 "\n (parameter %s)", sp
->index
, filename
,
3453 fieldname
, nameptr
);
3456 if (sp2
->index
< sp
->index
&&
3457 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3458 sp
->next
= sp2
->next
;
3465 inform("Internal error: cannot insert partial param "
3466 "in message %s's %s: field\n (parameter %s)",
3467 filename
, fieldname
, nameptr
);
3473 * Save our charset and lang tags.
3476 if (index
== 0 && encoded
) {
3478 pp
->charset
= charset
;
3483 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3484 pm
->pm_charset
= charset
;
3488 while (isspace ((unsigned char) *cp
))
3492 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3498 * Now that we're done, reassemble all of the partial parameters.
3501 for (pp
= phead
; pp
!= NULL
; ) {
3505 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3506 if (sp
->index
!= pindex
++) {
3507 inform("missing section %d for parameter in message "
3508 "%s's %s: field\n (parameter %s)", pindex
- 1,
3509 filename
, fieldname
, pp
->name
);
3515 p
= q
= mh_xmalloc(tlen
+ 1);
3516 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3517 memcpy(q
, sp
->value
, sp
->len
);
3527 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3528 pm
->pm_charset
= pp
->charset
;
3529 pm
->pm_lang
= pp
->lang
;
3540 * Return the charset for a particular content type.
3544 content_charset (CT ct
)
3546 char *ret_charset
= NULL
;
3548 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3550 return ret_charset
? ret_charset
: mh_xstrdup("US-ASCII");
3555 * Create a string based on a list of output parameters. Assume that this
3556 * parameter string will be appended to an existing header, so start out
3557 * with the separator (;). Perform RFC 2231 encoding when necessary.
3561 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3563 char *paramout
= NULL
;
3564 char line
[CPERLIN
* 2], *q
;
3565 int curlen
, index
, cont
, encode
, i
;
3566 size_t valoff
, numchars
;
3568 while (params
!= NULL
) {
3574 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3577 if (strlen(params
->pm_name
) > CPERLIN
) {
3578 inform("Parameter name \"%s\" is too long", params
->pm_name
);
3583 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3586 * Loop until we get a parameter that fits within a line. We
3587 * assume new lines start with a tab, so check our overflow based
3597 * At this point we're definitely continuing the line, so
3598 * be sure to include the parameter name and section index.
3601 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3602 params
->pm_name
, index
);
3605 * Both of these functions do a NUL termination
3609 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3610 numchars
, valoff
, index
);
3612 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3622 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3627 * "line" starts with a ;\n\t, so that doesn't count against
3628 * the length. But add 8 since it starts with a tab; that's
3629 * how we end up with 5.
3632 initialwidth
= strlen(line
) + 5;
3635 * At this point the line should be built, so add it to our
3636 * current output buffer.
3639 paramout
= add(line
, paramout
);
3643 * If this won't fit on the line, start a new one. Save room in
3644 * case we need a semicolon on the end
3647 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3659 * At this point, we're either finishing a continued parameter, or
3660 * we're working on a new one.
3664 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3665 params
->pm_name
, index
);
3667 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3672 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3673 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3675 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3676 strlen(params
->pm_value
+ valoff
), valoff
);
3683 paramout
= add(line
, paramout
);
3684 initialwidth
+= strlen(line
);
3686 params
= params
->pm_next
;
3690 *offsetout
= initialwidth
;
3696 * Calculate the size of a parameter.
3700 * pm - The parameter being output
3701 * index - If continuing the parameter, the index of the section
3703 * valueoff - The current offset into the parameter value that we're
3704 * working on (previous sections have consumed valueoff bytes).
3705 * encode - Set if we should perform encoding on this parameter section
3706 * (given that we're consuming bytesfit bytes).
3707 * cont - Set if the remaining data in value will not fit on a single
3708 * line and will need to be continued.
3709 * bytesfit - The number of bytes that we can consume from the parameter
3710 * value and still fit on a completely new line. The
3711 * calculation assumes the new line starts with a tab,
3712 * includes the parameter name and any encoding, and fits
3713 * within CPERLIN bytes. Will always be at least 1.
3717 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3720 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3721 size_t len
= 0, fit
= 0;
3722 int fitlimit
= 0, eightbit
, maxfit
;
3727 * Add up the length. First, start with the parameter name.
3730 len
= strlen(pm
->pm_name
);
3733 * Scan the parameter value and see if we need to do encoding for this
3737 eightbit
= contains8bit(start
, NULL
);
3740 * Determine if we need to encode this section. Encoding is necessary if:
3742 * - There are any 8-bit characters at all and we're on the first
3744 * - There are 8-bit characters within N bytes of our section start.
3745 * N is calculated based on the number of bytes it would take to
3746 * reach CPERLIN. Specifically:
3747 * 8 (starting tab) +
3748 * strlen(param name) +
3749 * 4 ('* for section marker, '=', opening/closing '"')
3751 * is the number of bytes used by everything that isn't part of the
3752 * value. So that gets subtracted from CPERLIN.
3755 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3756 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3757 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3761 len
++; /* Add in equal sign */
3765 * We're using maxfit as a marker for how many characters we can
3766 * fit into the line. Bump it by two because we're not using quotes
3773 * If we don't have a charset or language tag in this parameter,
3777 if (! pm
->pm_charset
) {
3778 pm
->pm_charset
= mh_xstrdup(write_charset_8bit());
3779 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3780 die("8-bit characters in parameter \"%s\", but "
3781 "local character set is US-ASCII", pm
->pm_name
);
3784 pm
->pm_lang
= mh_xstrdup(""); /* Default to a blank lang tag */
3786 len
++; /* For the encoding marker */
3789 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3794 * We know we definitely need to include an index. maxfit already
3795 * includes the section marker.
3797 len
+= strlen(indexchar
);
3799 for (p
= start
; *p
!= '\0'; p
++) {
3800 if (isparamencode(*p
)) {
3808 * Just so there's no confusion: maxfit is counting OUTPUT
3809 * characters (post-encoding). fit is counting INPUT characters.
3811 if (! fitlimit
&& maxfit
>= 0)
3813 else if (! fitlimit
)
3818 * Calculate the string length, but add room for quoting \
3819 * and " if necessary. Also account for quotes at beginning
3822 for (p
= start
; *p
!= '\0'; p
++) {
3833 if (! fitlimit
&& maxfit
>= 0)
3835 else if (! fitlimit
)
3852 * Output an encoded parameter string.
3856 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3857 size_t valueoff
, int index
)
3859 size_t outlen
= 0, n
;
3860 char *endptr
= output
+ len
, *p
;
3863 * First, output the marker for an encoded string.
3871 * If the index is 0, output the character set and language tag.
3872 * If theses were NULL, they should have already been filled in
3877 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
3881 if (output
> endptr
) {
3882 inform("Internal error: parameter buffer overflow");
3888 * Copy over the value, encoding if necessary
3891 p
= pm
->pm_value
+ valueoff
;
3892 while (valuelen
-- > 0) {
3893 if (isparamencode(*p
)) {
3894 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
3901 if (output
> endptr
) {
3902 inform("Internal error: parameter buffer overflow");
3913 * Output a "normal" parameter, without encoding. Be sure to escape
3914 * quotes and backslashes if necessary.
3918 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
3922 char *endptr
= output
+ len
, *p
;
3928 p
= pm
->pm_value
+ valueoff
;
3930 while (valuelen
-- > 0) {
3941 if (output
> endptr
) {
3942 inform("Internal error: parameter buffer overflow");
3947 if (output
- 2 > endptr
) {
3948 inform("Internal error: parameter buffer overflow");
3959 * Add a parameter to the parameter linked list
3963 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
3968 pm
->pm_name
= nocopy
? name
: getcpy(name
);
3969 pm
->pm_value
= nocopy
? value
: getcpy(value
);
3972 (*last
)->pm_next
= pm
;
3983 * Either replace a current parameter with a new value, or add the parameter
3984 * to the parameter linked list.
3988 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
3992 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
3993 if (strcasecmp(name
, pm
->pm_name
) == 0) {
3995 * If nocopy is set, it's assumed that we own both name
3996 * and value. We don't need name, so we discard it now.
4001 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4006 return add_param(first
, last
, name
, value
, nocopy
);
4010 * Retrieve a parameter value from a parameter linked list. If the parameter
4011 * value needs converted to the local character set, do that now.
4015 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4017 while (first
!= NULL
) {
4018 if (strcasecmp(name
, first
->pm_name
) == 0) {
4020 return first
->pm_value
;
4021 return getcpy(get_param_value(first
, replace
));
4023 first
= first
->pm_next
;
4030 * Return a parameter value, converting to the local character set if
4035 get_param_value(PM pm
, char replace
)
4037 static char buffer
[4096]; /* I hope no parameters are larger */
4038 size_t bufsize
= sizeof(buffer
);
4043 ICONV_CONST
char *p
;
4044 #else /* HAVE_ICONV */
4046 #endif /* HAVE_ICONV */
4051 * If we don't have a character set indicated, it's assumed to be
4052 * US-ASCII. If it matches our character set, we don't need to convert
4056 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4057 strlen(pm
->pm_charset
))) {
4058 return pm
->pm_value
;
4062 * In this case, we need to convert. If we have iconv support, use
4063 * that. Otherwise, go through and simply replace every non-ASCII
4064 * character with the substitution character.
4069 bufsize
= sizeof(buffer
);
4070 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4072 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4073 if (cd
== (iconv_t
) -1) {
4077 inbytes
= strlen(pm
->pm_value
);
4081 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4082 if (errno
!= EILSEQ
) {
4087 * Reset shift state, substitute our character,
4088 * try to restart conversion.
4091 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4104 for (++p
, --inbytes
;
4105 inbytes
> 0 && (((unsigned char) *p
) & 0xc0) == 0x80;
4124 #endif /* HAVE_ICONV */
4127 * Take everything non-ASCII and substitute the replacement character
4131 bufsize
= sizeof(buffer
);
4132 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4133 if (isascii((unsigned char) *p
) && isprint((unsigned char) *p
))