3 * mhparse.c -- routines to parse the contents of MIME messages
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
12 #include <h/signals.h>
17 #include <h/mhparse.h>
21 #endif /* HAVE_ICONV */
27 extern int rcachesw
; /* mhcachesbr.c */
28 extern int wcachesw
; /* mhcachesbr.c */
30 int checksw
= 0; /* check Content-MD5 field */
33 * These are for mhfixmsg to:
34 * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
36 * 2) Suppress the warning about bogus multipart content, and report it.
37 * 3) Suppress the warning about extraneous trailing ';' in header parameter
38 * lists, and report it.
40 int skip_mp_cte_check
;
41 int suppress_bogus_mp_content_warning
;
43 int suppress_extraneous_trailing_semicolon_warning
;
44 int extraneous_trailing_semicolon
;
47 * Structures for TEXT messages
49 struct k2v SubText
[] = {
50 { "plain", TEXT_PLAIN
},
51 { "richtext", TEXT_RICHTEXT
}, /* defined in RFC-1341 */
52 { "enriched", TEXT_ENRICHED
}, /* defined in RFC-1896 */
53 { NULL
, TEXT_UNKNOWN
} /* this one must be last! */
56 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
59 * Structures for MULTIPART messages
61 struct k2v SubMultiPart
[] = {
62 { "mixed", MULTI_MIXED
},
63 { "alternative", MULTI_ALTERNATE
},
64 { "digest", MULTI_DIGEST
},
65 { "parallel", MULTI_PARALLEL
},
66 { "related", MULTI_RELATED
},
67 { NULL
, MULTI_UNKNOWN
} /* this one must be last! */
71 * Structures for MESSAGE messages
73 struct k2v SubMessage
[] = {
74 { "rfc822", MESSAGE_RFC822
},
75 { "partial", MESSAGE_PARTIAL
},
76 { "external-body", MESSAGE_EXTERNAL
},
77 { NULL
, MESSAGE_UNKNOWN
} /* this one must be last! */
81 * Structure for APPLICATION messages
83 struct k2v SubApplication
[] = {
84 { "octet-stream", APPLICATION_OCTETS
},
85 { "postscript", APPLICATION_POSTSCRIPT
},
86 { NULL
, APPLICATION_UNKNOWN
} /* this one must be last! */
90 * Mapping of names of CTE types in mhbuild directives
92 static struct k2v EncodingType
[] = {
96 { "quoted-printable", CE_QUOTED
},
98 { "base64", CE_BASE64
},
104 int find_cache (CT
, int, int *, char *, char *, int);
107 int part_ok (CT
, int);
108 int type_ok (CT
, int);
109 void content_error (char *, CT
, char *, ...);
112 void free_encoding (CT
, int);
117 static CT
get_content (FILE *, char *, int);
118 static int get_comment (const char *, const char *, char **, char **);
120 static int InitGeneric (CT
);
121 static int InitText (CT
);
122 static int InitMultiPart (CT
);
123 static void reverse_parts (CT
);
124 static int InitMessage (CT
);
125 static int InitApplication (CT
);
126 static int init_encoding (CT
, OpenCEFunc
);
127 static unsigned long size_encoding (CT
);
128 static int InitBase64 (CT
);
129 static int openBase64 (CT
, char **);
130 static int InitQuoted (CT
);
131 static int openQuoted (CT
, char **);
132 static int Init7Bit (CT
);
133 static int openExternal (CT
, CT
, CE
, char **, int *);
134 static int InitFile (CT
);
135 static int openFile (CT
, char **);
136 static int InitFTP (CT
);
137 static int openFTP (CT
, char **);
138 static int InitMail (CT
);
139 static int openMail (CT
, char **);
140 static int readDigest (CT
, char *);
141 static int get_leftover_mp_content (CT
, int);
142 static int InitURL (CT
);
143 static int openURL (CT
, char **);
144 static int parse_header_attrs (const char *, const char *, char **, PM
*,
146 static size_t param_len(PM
, int, size_t, int *, int *, size_t *);
147 static size_t encode_param(PM
, char *, size_t, size_t, size_t, int);
148 static size_t normal_param(PM
, char *, size_t, size_t, size_t);
149 static int get_dispo (char *, CT
, int);
151 struct str2init str2cts
[] = {
152 { "application", CT_APPLICATION
, InitApplication
},
153 { "audio", CT_AUDIO
, InitGeneric
},
154 { "image", CT_IMAGE
, InitGeneric
},
155 { "message", CT_MESSAGE
, InitMessage
},
156 { "multipart", CT_MULTIPART
, InitMultiPart
},
157 { "text", CT_TEXT
, InitText
},
158 { "video", CT_VIDEO
, InitGeneric
},
159 { NULL
, CT_EXTENSION
, NULL
}, /* these two must be last! */
160 { NULL
, CT_UNKNOWN
, NULL
},
163 struct str2init str2ces
[] = {
164 { "base64", CE_BASE64
, InitBase64
},
165 { "quoted-printable", CE_QUOTED
, InitQuoted
},
166 { "8bit", CE_8BIT
, Init7Bit
},
167 { "7bit", CE_7BIT
, Init7Bit
},
168 { "binary", CE_BINARY
, Init7Bit
},
169 { NULL
, CE_EXTENSION
, NULL
}, /* these two must be last! */
170 { NULL
, CE_UNKNOWN
, NULL
},
174 * NOTE WELL: si_key MUST NOT have value of NOTOK
176 * si_key is 1 if access method is anonymous.
178 struct str2init str2methods
[] = {
179 { "afs", 1, InitFile
},
180 { "anon-ftp", 1, InitFTP
},
181 { "ftp", 0, InitFTP
},
182 { "local-file", 0, InitFile
},
183 { "mail-server", 0, InitMail
},
184 { "url", 0, InitURL
},
190 * Main entry point for parsing a MIME message or file.
191 * It returns the Content structure for the top level
192 * entity in the file.
196 parse_mime (char *file
)
205 * Check if file is actually standard input
207 if ((is_stdin
= !(strcmp (file
, "-")))) {
208 char *tfile
= m_mktemp2(NULL
, invo_name
, NULL
, &fp
);
210 advise("mhparse", "unable to create temporary file in %s",
214 file
= add (tfile
, NULL
);
216 while ((n
= fread(buffer
, 1, sizeof(buffer
), stdin
)) > 0) {
217 if (fwrite(buffer
, 1, n
, fp
) != n
) {
218 (void) m_unlink (file
);
219 advise (file
, "error copying to temporary file");
225 if (ferror (stdin
)) {
226 (void) m_unlink (file
);
227 advise ("stdin", "error reading");
231 (void) m_unlink (file
);
232 advise (file
, "error writing");
235 fseek (fp
, 0L, SEEK_SET
);
236 } else if ((fp
= fopen (file
, "r")) == NULL
) {
237 advise (file
, "unable to read");
241 if (!(ct
= get_content (fp
, file
, 1))) {
243 (void) m_unlink (file
);
244 advise (NULL
, "unable to decode %s", file
);
249 ct
->c_unlink
= 1; /* temp file to remove */
253 if (ct
->c_end
== 0L) {
254 fseek (fp
, 0L, SEEK_END
);
255 ct
->c_end
= ftell (fp
);
258 if (ct
->c_ctinitfnx
&& (*ct
->c_ctinitfnx
) (ct
) == NOTOK
) {
270 * Main routine for reading/parsing the headers
271 * of a message content.
273 * toplevel = 1 # we are at the top level of the message
274 * toplevel = 0 # we are inside message type or multipart type
275 * # other than multipart/digest
276 * toplevel = -1 # we are inside multipart/digest
277 * NB: on failure we will fclose(in)!
281 get_content (FILE *in
, char *file
, int toplevel
)
284 char buf
[BUFSIZ
], name
[NAMESZ
];
288 m_getfld_state_t gstate
= 0;
290 /* allocate the content structure */
291 if (!(ct
= (CT
) mh_xcalloc (1, sizeof(*ct
))))
292 adios (NULL
, "out of memory");
295 ct
->c_file
= add (file
, NULL
);
296 ct
->c_begin
= ftell (ct
->c_fp
) + 1;
299 * Parse the header fields for this
300 * content into a linked list.
302 m_getfld_track_filepos (&gstate
, in
);
303 for (compnum
= 1;;) {
304 int bufsz
= sizeof buf
;
305 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
310 /* get copies of the buffers */
311 np
= add (name
, NULL
);
312 vp
= add (buf
, NULL
);
314 /* if necessary, get rest of field */
315 while (state
== FLDPLUS
) {
317 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
318 vp
= add (buf
, vp
); /* add to previous value */
321 /* Now add the header data to the list */
322 add_header (ct
, np
, vp
);
324 /* continue, to see if this isn't the last header field */
325 ct
->c_begin
= ftell (in
) + 1;
329 ct
->c_begin
= ftell (in
) - strlen (buf
);
333 ct
->c_begin
= ftell (in
);
338 adios (NULL
, "message format error in component #%d", compnum
);
341 adios (NULL
, "getfld() returned %d", state
);
344 /* break out of the loop */
347 m_getfld_state_destroy (&gstate
);
350 * Read the content headers. We will parse the
351 * MIME related header fields into their various
352 * structures and set internal flags related to
353 * content type/subtype, etc.
356 hp
= ct
->c_first_hf
; /* start at first header field */
358 /* Get MIME-Version field */
359 if (!strcasecmp (hp
->name
, VRSN_FIELD
)) {
364 advise (NULL
, "message %s has multiple %s: fields",
365 ct
->c_file
, VRSN_FIELD
);
368 ct
->c_vrsn
= add (hp
->value
, NULL
);
370 /* Now, cleanup this field */
373 while (isspace ((unsigned char) *cp
))
375 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
377 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
378 if (!isspace ((unsigned char) *dp
))
382 fprintf (stderr
, "%s: %s\n", VRSN_FIELD
, cp
);
385 get_comment (ct
->c_file
, VRSN_FIELD
, &cp
, NULL
) == NOTOK
)
388 for (dp
= cp
; istoken (*dp
); dp
++)
392 ucmp
= !strcasecmp (cp
, VRSN_VALUE
);
395 admonish (NULL
, "message %s has unknown value for %s: field (%s)",
396 ct
->c_file
, VRSN_FIELD
, cp
);
399 else if (!strcasecmp (hp
->name
, TYPE_FIELD
)) {
400 /* Get Content-Type field */
401 struct str2init
*s2i
;
402 CI ci
= &ct
->c_ctinfo
;
404 /* Check if we've already seen a Content-Type header */
406 advise (NULL
, "message %s has multiple %s: fields",
407 ct
->c_file
, TYPE_FIELD
);
411 /* Parse the Content-Type field */
412 if (get_ctinfo (hp
->value
, ct
, 0) == NOTOK
)
416 * Set the Init function and the internal
417 * flag for this content type.
419 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
420 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
422 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
424 ct
->c_type
= s2i
->si_val
;
425 ct
->c_ctinitfnx
= s2i
->si_init
;
427 else if (!strcasecmp (hp
->name
, ENCODING_FIELD
)) {
428 /* Get Content-Transfer-Encoding field */
430 struct str2init
*s2i
;
433 * Check if we've already seen the
434 * Content-Transfer-Encoding field
437 advise (NULL
, "message %s has multiple %s: fields",
438 ct
->c_file
, ENCODING_FIELD
);
442 /* get copy of this field */
443 ct
->c_celine
= cp
= add (hp
->value
, NULL
);
445 while (isspace ((unsigned char) *cp
))
447 for (dp
= cp
; istoken (*dp
); dp
++)
453 * Find the internal flag and Init function
454 * for this transfer encoding.
456 for (s2i
= str2ces
; s2i
->si_key
; s2i
++)
457 if (!strcasecmp (cp
, s2i
->si_key
))
459 if (!s2i
->si_key
&& !uprf (cp
, "X-"))
462 ct
->c_encoding
= s2i
->si_val
;
464 /* Call the Init function for this encoding */
465 if (s2i
->si_init
&& (*s2i
->si_init
) (ct
) == NOTOK
)
468 else if (!strcasecmp (hp
->name
, MD5_FIELD
)) {
469 /* Get Content-MD5 field */
475 if (ct
->c_digested
) {
476 advise (NULL
, "message %s has multiple %s: fields",
477 ct
->c_file
, MD5_FIELD
);
481 ep
= cp
= add (hp
->value
, NULL
); /* get a copy */
483 while (isspace ((unsigned char) *cp
))
485 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
487 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
488 if (!isspace ((unsigned char) *dp
))
492 fprintf (stderr
, "%s: %s\n", MD5_FIELD
, cp
);
495 get_comment (ct
->c_file
, MD5_FIELD
, &cp
, NULL
) == NOTOK
) {
500 for (dp
= cp
; *dp
&& !isspace ((unsigned char) *dp
); dp
++)
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 */
576 hp
= mh_xmalloc (sizeof(*hp
));
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
= add (cp
, NULL
);
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
++)
636 ci
->ci_type
= add (cp
, NULL
); /* store content type */
640 advise (NULL
, "invalid %s: field in message %s (empty type)",
641 TYPE_FIELD
, ct
->c_file
);
645 /* down case the content type string */
646 for (dp
= ci
->ci_type
; *dp
; dp
++)
647 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
648 *dp
= tolower ((unsigned char) *dp
);
650 while (isspace ((unsigned char) *cp
))
653 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
654 &ci
->ci_comment
) == NOTOK
)
659 ci
->ci_subtype
= add ("", NULL
);
664 while (isspace ((unsigned char) *cp
))
667 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
668 &ci
->ci_comment
) == NOTOK
)
671 for (dp
= cp
; istoken (*dp
); dp
++)
674 ci
->ci_subtype
= add (cp
, NULL
); /* store the content subtype */
677 if (!*ci
->ci_subtype
) {
679 "invalid %s: field in message %s (empty subtype for \"%s\")",
680 TYPE_FIELD
, ct
->c_file
, ci
->ci_type
);
684 /* down case the content subtype string */
685 for (dp
= ci
->ci_subtype
; *dp
; dp
++)
686 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
687 *dp
= tolower ((unsigned char) *dp
);
690 while (isspace ((unsigned char) *cp
))
693 if (*cp
== '(' && get_comment (ct
->c_file
, TYPE_FIELD
, &cp
,
694 &ci
->ci_comment
) == NOTOK
)
697 if ((status
= parse_header_attrs (ct
->c_file
, TYPE_FIELD
, &cp
,
698 &ci
->ci_first_pm
, &ci
->ci_last_pm
,
699 &ci
->ci_comment
)) != OK
) {
700 return status
== NOTOK
? NOTOK
: OK
;
704 * Get any <Content-Id> given in buffer
706 if (magic
&& *cp
== '<') {
711 if (!(dp
= strchr(ct
->c_id
= ++cp
, '>'))) {
712 advise (NULL
, "invalid ID in message %s", ct
->c_file
);
718 ct
->c_id
= concat ("<", ct
->c_id
, ">\n", NULL
);
724 while (isspace ((unsigned char) *cp
))
729 * Get any [Content-Description] given in buffer.
731 if (magic
&& *cp
== '[') {
733 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
737 advise (NULL
, "invalid description in message %s", ct
->c_file
);
745 ct
->c_descr
= concat (ct
->c_descr
, "\n", NULL
);
751 while (isspace ((unsigned char) *cp
))
756 * Get any {Content-Disposition} given in buffer.
758 if (magic
&& *cp
== '{') {
760 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
764 advise (NULL
, "invalid disposition in message %s", ct
->c_file
);
772 if (get_dispo(cp
, ct
, 1) != OK
)
778 while (isspace ((unsigned char) *cp
))
783 * Get any extension directives (right now just the content transfer
784 * encoding, but maybe others) that we care about.
787 if (magic
&& *cp
== '*') {
789 * See if it's a CTE we match on
794 while (*cp
!= '\0' && ! isspace((unsigned char) *cp
))
798 advise (NULL
, "invalid null transfer encoding specification");
805 ct
->c_reqencoding
= CE_UNKNOWN
;
807 for (kv
= EncodingType
; kv
->kv_key
; kv
++) {
808 if (strcasecmp(kv
->kv_key
, dp
) == 0) {
809 ct
->c_reqencoding
= kv
->kv_value
;
814 if (ct
->c_reqencoding
== CE_UNKNOWN
) {
815 advise (NULL
, "invalid CTE specification: \"%s\"", dp
);
819 while (isspace ((unsigned char) *cp
))
824 * Check if anything is left over
828 ci
->ci_magic
= add (cp
, NULL
);
830 /* If there is a Content-Disposition header and it doesn't
831 have a *filename=, extract it from the magic contents.
832 The r1bindex call skips any leading directory
834 if (ct
->c_dispo_type
&&
835 !get_param(ct
->c_dispo_first
, "filename", '_', 1)) {
836 add_param(&ct
->c_dispo_first
, &ct
->c_dispo_last
, "filename",
837 r1bindex(ci
->ci_magic
, '/'), 0);
842 "extraneous information in message %s's %s: field\n%*s(%s)",
843 ct
->c_file
, TYPE_FIELD
, strlen(invo_name
) + 2, "", cp
);
851 * Parse out a Content-Disposition header. A lot of this is cribbed from
855 get_dispo (char *cp
, CT ct
, int buildflag
)
857 char *dp
, *dispoheader
;
862 * Save the whole copy of the Content-Disposition header, unless we're
863 * processing a mhbuild directive. A NULL c_dispo will be a flag to
864 * mhbuild that the disposition header needs to be generated at that
868 dispoheader
= cp
= add(cp
, NULL
);
870 while (isspace ((unsigned char) *cp
)) /* trim leading spaces */
873 /* change newlines to spaces */
874 for (dp
= strchr(cp
, '\n'); dp
; dp
= strchr(dp
, '\n'))
877 /* trim trailing spaces */
878 for (dp
= cp
+ strlen (cp
) - 1; dp
>= cp
; dp
--)
879 if (!isspace ((unsigned char) *dp
))
884 fprintf (stderr
, "%s: %s\n", DISPO_FIELD
, cp
);
886 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) ==
892 for (dp
= cp
; istoken (*dp
); dp
++)
895 ct
->c_dispo_type
= add (cp
, NULL
); /* store disposition type */
898 if (*cp
== '(' && get_comment (ct
->c_file
, DISPO_FIELD
, &cp
, NULL
) == NOTOK
)
901 if ((status
= parse_header_attrs (ct
->c_file
, DISPO_FIELD
, &cp
,
902 &ct
->c_dispo_first
, &ct
->c_dispo_last
,
904 if (status
== NOTOK
) {
910 "extraneous information in message %s's %s: field\n%*s(%s)",
911 ct
->c_file
, DISPO_FIELD
, strlen(invo_name
) + 2, "", cp
);
917 ct
->c_dispo
= dispoheader
;
924 get_comment (const char *filename
, const char *fieldname
, char **ap
,
929 char c
, buffer
[BUFSIZ
], *dp
;
939 advise (NULL
, "invalid comment in message %s's %s: field",
940 filename
, fieldname
);
945 if ((c
= *cp
++) == '\0')
968 if ((dp
= *commentp
)) {
969 *commentp
= concat (dp
, " ", buffer
, NULL
);
972 *commentp
= add (buffer
, NULL
);
976 while (isspace ((unsigned char) *cp
))
987 * Handles content types audio, image, and video.
988 * There's not much to do right here.
996 return OK
; /* not much to do here */
1007 char buffer
[BUFSIZ
];
1012 CI ci
= &ct
->c_ctinfo
;
1014 /* check for missing subtype */
1015 if (!*ci
->ci_subtype
)
1016 ci
->ci_subtype
= add ("plain", ci
->ci_subtype
);
1019 ct
->c_subtype
= ct_str_subtype (CT_TEXT
, ci
->ci_subtype
);
1021 /* allocate text character set structure */
1022 if ((t
= (struct text
*) mh_xcalloc (1, sizeof(*t
))) == NULL
)
1023 adios (NULL
, "out of memory");
1024 ct
->c_ctparams
= (void *) t
;
1026 /* scan for charset parameter */
1027 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
)
1028 if (!strcasecmp (pm
->pm_name
, "charset"))
1031 /* check if content specified a character set */
1033 chset
= pm
->pm_value
;
1034 t
->tx_charset
= CHARSET_SPECIFIED
;
1036 t
->tx_charset
= CHARSET_UNSPECIFIED
;
1040 * If we can not handle character set natively,
1041 * then check profile for string to modify the
1042 * terminal or display method.
1044 * termproc is for mhshow, though mhlist -debug prints it, too.
1046 if (chset
!= NULL
&& !check_charset (chset
, strlen (chset
))) {
1047 snprintf (buffer
, sizeof(buffer
), "%s-charset-%s", invo_name
, chset
);
1048 if ((cp
= context_find (buffer
)))
1049 ct
->c_termproc
= getcpy (cp
);
1061 InitMultiPart (CT ct
)
1071 struct multipart
*m
;
1072 struct part
*part
, **next
;
1073 CI ci
= &ct
->c_ctinfo
;
1078 * The encoding for multipart messages must be either
1079 * 7bit, 8bit, or binary (per RFC2045).
1081 if (! skip_mp_cte_check
&& ct
->c_encoding
!= CE_7BIT
&&
1082 ct
->c_encoding
!= CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
) {
1083 /* Copy the Content-Transfer-Encoding header field body so we can
1084 remove any trailing whitespace and leading blanks from it. */
1085 char *cte
= add (ct
->c_celine
? ct
->c_celine
: "(null)", NULL
);
1087 bp
= cte
+ strlen (cte
) - 1;
1088 while (bp
>= cte
&& isspace ((unsigned char) *bp
)) *bp
-- = '\0';
1089 for (bp
= cte
; *bp
&& isblank ((unsigned char) *bp
); ++bp
) continue;
1092 "\"%s/%s\" type in message %s must be encoded in\n"
1093 "7bit, 8bit, or binary, per RFC 2045 (6.4). One workaround "
1094 "is to\nmanually edit the file and change the \"%s\"\n"
1095 "Content-Transfer-Encoding to one of those. For now",
1096 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, bp
);
1103 ct
->c_subtype
= ct_str_subtype (CT_MULTIPART
, ci
->ci_subtype
);
1106 * Check for "boundary" parameter, which is
1107 * required for multipart messages.
1110 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1111 if (!strcasecmp (pm
->pm_name
, "boundary")) {
1117 /* complain if boundary parameter is missing */
1120 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1121 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1125 /* allocate primary structure for multipart info */
1126 if ((m
= (struct multipart
*) mh_xcalloc (1, sizeof(*m
))) == NULL
)
1127 adios (NULL
, "out of memory");
1128 ct
->c_ctparams
= (void *) m
;
1130 /* check if boundary parameter contains only whitespace characters */
1131 for (cp
= bp
; isspace ((unsigned char) *cp
); cp
++)
1134 advise (NULL
, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1135 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1139 /* remove trailing whitespace from boundary parameter */
1140 for (cp
= bp
, dp
= cp
+ strlen (cp
) - 1; dp
> cp
; dp
--)
1141 if (!isspace ((unsigned char) *dp
))
1145 /* record boundary separators */
1146 m
->mp_start
= concat (bp
, "\n", NULL
);
1147 m
->mp_stop
= concat (bp
, "--\n", NULL
);
1149 if (!ct
->c_fp
&& (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1150 advise (ct
->c_file
, "unable to open for reading");
1154 fseek (fp
= ct
->c_fp
, pos
= ct
->c_begin
, SEEK_SET
);
1156 next
= &m
->mp_parts
;
1160 while ((gotlen
= getline(&bufp
, &buflen
, fp
)) != -1) {
1165 if (bufp
[0] != '-' || bufp
[1] != '-')
1168 if (strcmp (bufp
+ 2, m
->mp_start
))
1171 if ((part
= (struct part
*) mh_xcalloc (1, sizeof(*part
))) == NULL
)
1172 adios (NULL
, "out of memory");
1174 next
= &part
->mp_next
;
1176 if (!(p
= get_content (fp
, ct
->c_file
,
1177 ct
->c_subtype
== MULTI_DIGEST
? -1 : 0))) {
1185 fseek (fp
, pos
, SEEK_SET
);
1188 if (strcmp (bufp
+ 2, m
->mp_start
) == 0) {
1192 p
->c_end
= ftell(fp
) - (gotlen
+ 1);
1193 if (p
->c_end
< p
->c_begin
)
1194 p
->c_begin
= p
->c_end
;
1199 if (strcmp (bufp
+ 2, m
->mp_stop
) == 0)
1205 if (! suppress_bogus_mp_content_warning
) {
1206 advise (NULL
, "bogus multipart content in message %s", ct
->c_file
);
1208 bogus_mp_content
= 1;
1210 if (!inout
&& part
) {
1212 p
->c_end
= ct
->c_end
;
1214 if (p
->c_begin
>= p
->c_end
) {
1215 for (next
= &m
->mp_parts
; *next
!= part
;
1216 next
= &((*next
)->mp_next
))
1220 free ((char *) part
);
1225 /* reverse the order of the parts for multipart/alternative */
1226 if (ct
->c_subtype
== MULTI_ALTERNATE
)
1230 * label all subparts with part number, and
1231 * then initialize the content of the subpart.
1236 char partnam
[BUFSIZ
];
1239 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
1240 pp
= partnam
+ strlen (partnam
);
1245 for (part
= m
->mp_parts
, partnum
= 1; part
;
1246 part
= part
->mp_next
, partnum
++) {
1249 sprintf (pp
, "%d", partnum
);
1250 p
->c_partno
= add (partnam
, NULL
);
1252 /* initialize the content of the subparts */
1253 if (p
->c_ctinitfnx
&& (*p
->c_ctinitfnx
) (p
) == NOTOK
) {
1262 get_leftover_mp_content (ct
, 1);
1263 get_leftover_mp_content (ct
, 0);
1273 * reverse the order of the parts of a multipart/alternative
1277 reverse_parts (CT ct
)
1279 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1283 /* Reverse the order of its parts by walking the mp_parts list
1284 and pushing each node to the front. */
1285 for (part
= m
->mp_parts
, m
->mp_parts
= NULL
; part
; part
= next
) {
1286 next
= part
->mp_next
;
1287 part
->mp_next
= m
->mp_parts
;
1295 /* parse_mime() arranges alternates in reverse (priority) order. This
1296 function can be used to reverse them back. This will put, for
1297 example, a text/plain part before a text/html part in a
1298 multipart/alternative part, for example, where it belongs. */
1300 reverse_alternative_parts (CT ct
) {
1301 if (ct
->c_type
== CT_MULTIPART
) {
1302 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1305 if (ct
->c_subtype
== MULTI_ALTERNATE
) {
1309 /* And call recursively on each part of a multipart. */
1310 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1311 reverse_alternative_parts (part
->mp_part
);
1324 CI ci
= &ct
->c_ctinfo
;
1326 if ((ct
->c_encoding
!= CE_7BIT
) && (ct
->c_encoding
!= CE_8BIT
)) {
1328 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1329 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
);
1333 /* check for missing subtype */
1334 if (!*ci
->ci_subtype
)
1335 ci
->ci_subtype
= add ("rfc822", ci
->ci_subtype
);
1338 ct
->c_subtype
= ct_str_subtype (CT_MESSAGE
, ci
->ci_subtype
);
1340 switch (ct
->c_subtype
) {
1341 case MESSAGE_RFC822
:
1344 case MESSAGE_PARTIAL
:
1349 if ((p
= (struct partial
*) mh_xcalloc (1, sizeof(*p
))) == NULL
)
1350 adios (NULL
, "out of memory");
1351 ct
->c_ctparams
= (void *) p
;
1353 /* scan for parameters "id", "number", and "total" */
1354 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1355 if (!strcasecmp (pm
->pm_name
, "id")) {
1356 p
->pm_partid
= add (pm
->pm_value
, NULL
);
1359 if (!strcasecmp (pm
->pm_name
, "number")) {
1360 if (sscanf (pm
->pm_value
, "%d", &p
->pm_partno
) != 1
1361 || p
->pm_partno
< 1) {
1364 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1365 pm
->pm_name
, ci
->ci_type
, ci
->ci_subtype
,
1366 ct
->c_file
, TYPE_FIELD
);
1371 if (!strcasecmp (pm
->pm_name
, "total")) {
1372 if (sscanf (pm
->pm_value
, "%d", &p
->pm_maxno
) != 1
1381 || (p
->pm_maxno
&& p
->pm_partno
> p
->pm_maxno
)) {
1383 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1384 ci
->ci_type
, ci
->ci_subtype
,
1385 ct
->c_file
, TYPE_FIELD
);
1391 case MESSAGE_EXTERNAL
:
1398 if ((e
= (struct exbody
*) mh_xcalloc (1, sizeof(*e
))) == NULL
)
1399 adios (NULL
, "out of memory");
1400 ct
->c_ctparams
= (void *) e
;
1403 && (ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1404 advise (ct
->c_file
, "unable to open for reading");
1408 fseek (fp
= ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
1410 if (!(p
= get_content (fp
, ct
->c_file
, 0))) {
1418 p
->c_ceopenfnx
= NULL
;
1419 if ((exresult
= params_external (ct
, 0)) != NOTOK
1420 && p
->c_ceopenfnx
== openMail
) {
1424 if ((size
= ct
->c_end
- p
->c_begin
) <= 0) {
1426 content_error (NULL
, ct
,
1427 "empty body for access-type=mail-server");
1431 e
->eb_body
= bp
= mh_xmalloc ((unsigned) size
);
1432 fseek (p
->c_fp
, p
->c_begin
, SEEK_SET
);
1434 switch (cc
= fread (bp
, sizeof(*bp
), size
, p
->c_fp
)) {
1436 adios ("failed", "fread");
1439 adios (NULL
, "unexpected EOF from fread");
1442 bp
+= cc
, size
-= cc
;
1449 p
->c_end
= p
->c_begin
;
1454 if (exresult
== NOTOK
)
1456 if (e
->eb_flags
== NOTOK
)
1459 switch (p
->c_type
) {
1464 if (p
->c_subtype
!= MESSAGE_RFC822
)
1468 e
->eb_partno
= ct
->c_partno
;
1470 (*p
->c_ctinitfnx
) (p
);
1485 params_external (CT ct
, int composing
)
1488 struct exbody
*e
= (struct exbody
*) ct
->c_ctparams
;
1489 CI ci
= &ct
->c_ctinfo
;
1491 ct
->c_ceopenfnx
= NULL
;
1492 for (pm
= ci
->ci_first_pm
; pm
; pm
= pm
->pm_next
) {
1493 if (!strcasecmp (pm
->pm_name
, "access-type")) {
1494 struct str2init
*s2i
;
1495 CT p
= e
->eb_content
;
1497 for (s2i
= str2methods
; s2i
->si_key
; s2i
++)
1498 if (!strcasecmp (pm
->pm_value
, s2i
->si_key
))
1501 e
->eb_access
= pm
->pm_value
;
1502 e
->eb_flags
= NOTOK
;
1503 p
->c_encoding
= CE_EXTERNAL
;
1506 e
->eb_access
= s2i
->si_key
;
1507 e
->eb_flags
= s2i
->si_val
;
1508 p
->c_encoding
= CE_EXTERNAL
;
1510 /* Call the Init function for this external type */
1511 if ((*s2i
->si_init
)(p
) == NOTOK
)
1515 if (!strcasecmp (pm
->pm_name
, "name")) {
1516 e
->eb_name
= pm
->pm_value
;
1519 if (!strcasecmp (pm
->pm_name
, "permission")) {
1520 e
->eb_permission
= pm
->pm_value
;
1523 if (!strcasecmp (pm
->pm_name
, "site")) {
1524 e
->eb_site
= pm
->pm_value
;
1527 if (!strcasecmp (pm
->pm_name
, "directory")) {
1528 e
->eb_dir
= pm
->pm_value
;
1531 if (!strcasecmp (pm
->pm_name
, "mode")) {
1532 e
->eb_mode
= pm
->pm_value
;
1535 if (!strcasecmp (pm
->pm_name
, "size")) {
1536 sscanf (pm
->pm_value
, "%lu", &e
->eb_size
);
1539 if (!strcasecmp (pm
->pm_name
, "server")) {
1540 e
->eb_server
= pm
->pm_value
;
1543 if (!strcasecmp (pm
->pm_name
, "subject")) {
1544 e
->eb_subject
= pm
->pm_value
;
1547 if (!strcasecmp (pm
->pm_name
, "url")) {
1549 * According to RFC 2017, we have to remove all whitespace from
1553 char *u
, *p
= pm
->pm_value
;
1554 e
->eb_url
= u
= mh_xmalloc(strlen(pm
->pm_value
) + 1);
1556 for (; *p
!= '\0'; p
++) {
1557 if (! isspace((unsigned char) *p
))
1564 if (composing
&& !strcasecmp (pm
->pm_name
, "body")) {
1565 e
->eb_body
= getcpy (pm
->pm_value
);
1570 if (!e
->eb_access
) {
1572 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1573 ci
->ci_type
, ci
->ci_subtype
, ct
->c_file
, TYPE_FIELD
);
1586 InitApplication (CT ct
)
1588 CI ci
= &ct
->c_ctinfo
;
1591 ct
->c_subtype
= ct_str_subtype (CT_APPLICATION
, ci
->ci_subtype
);
1598 * TRANSFER ENCODINGS
1602 init_encoding (CT ct
, OpenCEFunc openfnx
)
1604 ct
->c_ceopenfnx
= openfnx
;
1605 ct
->c_ceclosefnx
= close_encoding
;
1606 ct
->c_cesizefnx
= size_encoding
;
1613 close_encoding (CT ct
)
1615 CE ce
= &ct
->c_cefile
;
1624 static unsigned long
1625 size_encoding (CT ct
)
1630 CE ce
= &ct
->c_cefile
;
1633 if (ce
->ce_fp
&& fstat (fileno (ce
->ce_fp
), &st
) != NOTOK
)
1634 return (long) st
.st_size
;
1637 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1638 return (long) st
.st_size
;
1643 if (ct
->c_encoding
== CE_EXTERNAL
)
1644 return (ct
->c_end
- ct
->c_begin
);
1647 if ((fd
= (*ct
->c_ceopenfnx
) (ct
, &file
)) == NOTOK
)
1648 return (ct
->c_end
- ct
->c_begin
);
1650 if (fstat (fd
, &st
) != NOTOK
)
1651 size
= (long) st
.st_size
;
1655 (*ct
->c_ceclosefnx
) (ct
);
1664 static unsigned char b642nib
[0x80] = {
1665 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1666 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1667 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1668 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1669 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1670 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1671 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1672 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1673 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
1674 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1675 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1676 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1677 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1678 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1679 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1680 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1687 return init_encoding (ct
, openBase64
);
1692 openBase64 (CT ct
, char **file
)
1694 int bitno
, cc
, digested
;
1695 int fd
, len
, skip
, own_ct_fp
= 0, text
= ct
->c_type
== CT_TEXT
;
1697 unsigned char value
, b
;
1698 char *cp
, *ep
, buffer
[BUFSIZ
];
1699 /* sbeck -- handle suffixes */
1701 CE ce
= &ct
->c_cefile
;
1705 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1710 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1711 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1717 if (*file
== NULL
) {
1720 ce
->ce_file
= add (*file
, NULL
);
1724 /* sbeck@cise.ufl.edu -- handle suffixes */
1726 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1727 if (ce
->ce_unlink
) {
1728 /* Create temporary file with filename extension. */
1729 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1730 adios(NULL
, "unable to create temporary file in %s",
1734 ce
->ce_file
= add (cp
, ce
->ce_file
);
1736 } else if (*file
== NULL
) {
1738 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1739 adios(NULL
, "unable to create temporary file in %s",
1742 ce
->ce_file
= add (tempfile
, NULL
);
1745 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1746 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1750 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1751 adios (NULL
, "internal error(1)");
1754 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1755 content_error (ct
->c_file
, ct
, "unable to open for reading");
1761 if ((digested
= ct
->c_digested
))
1762 MD5Init (&mdContext
);
1768 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
1770 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
1772 content_error (ct
->c_file
, ct
, "error reading from");
1776 content_error (NULL
, ct
, "premature eof");
1784 for (ep
= (cp
= buffer
) + cc
; cp
< ep
; cp
++) {
1787 if (isspace ((unsigned char) *cp
))
1789 if (skip
|| (((unsigned char) *cp
) & 0x80)
1790 || (value
= b642nib
[((unsigned char) *cp
) & 0x7f]) > 0x3f) {
1792 fprintf (stderr
, "*cp=0x%x pos=%ld skip=%d\n",
1793 (unsigned char) *cp
,
1794 (long) (lseek (fd
, (off_t
) 0, SEEK_CUR
) - (ep
- cp
)),
1797 content_error (NULL
, ct
,
1798 "invalid BASE64 encoding -- continuing");
1802 bits
|= value
<< bitno
;
1804 if ((bitno
-= 6) < 0) {
1805 b
= (bits
>> 16) & 0xff;
1806 if (!text
|| b
!= '\r')
1807 putc ((char) b
, ce
->ce_fp
);
1809 MD5Update (&mdContext
, &b
, 1);
1811 b
= (bits
>> 8) & 0xff;
1812 if (! text
|| b
!= '\r')
1813 putc ((char) b
, ce
->ce_fp
);
1815 MD5Update (&mdContext
, &b
, 1);
1818 if (! text
|| b
!= '\r')
1819 putc ((char) b
, ce
->ce_fp
);
1821 MD5Update (&mdContext
, &b
, 1);
1825 if (ferror (ce
->ce_fp
)) {
1826 content_error (ce
->ce_file
, ct
,
1827 "error writing to");
1830 bitno
= 18, bits
= 0L, skip
= 0;
1836 goto self_delimiting
;
1845 fprintf (stderr
, "premature ending (bitno %d)\n", bitno
);
1847 content_error (NULL
, ct
, "invalid BASE64 encoding");
1852 fseek (ct
->c_fp
, 0L, SEEK_SET
);
1854 if (fflush (ce
->ce_fp
)) {
1855 content_error (ce
->ce_file
, ct
, "error writing to");
1860 unsigned char digest
[16];
1862 MD5Final (digest
, &mdContext
);
1863 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
1864 sizeof(digest
) / sizeof(digest
[0])))
1865 content_error (NULL
, ct
,
1866 "content integrity suspect (digest mismatch) -- continuing");
1869 fprintf (stderr
, "content integrity confirmed\n");
1872 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1875 *file
= ce
->ce_file
;
1880 return fileno (ce
->ce_fp
);
1887 free_encoding (ct
, 0);
1896 static char hex2nib
[0x80] = {
1897 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1898 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1899 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1900 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1901 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1902 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1903 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1904 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1905 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1906 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1907 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1908 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1909 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1910 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1911 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1919 return init_encoding (ct
, openQuoted
);
1924 openQuoted (CT ct
, char **file
)
1926 int cc
, digested
, len
, quoted
, own_ct_fp
= 0;
1932 CE ce
= &ct
->c_cefile
;
1933 /* sbeck -- handle suffixes */
1938 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
1943 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
1944 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
1950 if (*file
== NULL
) {
1953 ce
->ce_file
= add (*file
, NULL
);
1957 /* sbeck@cise.ufl.edu -- handle suffixes */
1959 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
1960 if (ce
->ce_unlink
) {
1961 /* Create temporary file with filename extension. */
1962 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
1963 adios(NULL
, "unable to create temporary file in %s",
1967 ce
->ce_file
= add (cp
, ce
->ce_file
);
1969 } else if (*file
== NULL
) {
1971 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
1972 adios(NULL
, "unable to create temporary file in %s",
1975 ce
->ce_file
= add (tempfile
, NULL
);
1978 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
1979 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
1983 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
1984 adios (NULL
, "internal error(2)");
1987 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
1988 content_error (ct
->c_file
, ct
, "unable to open for reading");
1994 if ((digested
= ct
->c_digested
))
1995 MD5Init (&mdContext
);
2002 fseek (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2004 if ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) == -1) {
2005 content_error (NULL
, ct
, "premature eof");
2009 if ((cc
= gotlen
) > len
)
2013 for (ep
= (cp
= bufp
) + cc
- 1; cp
<= ep
; ep
--)
2014 if (!isspace ((unsigned char) *ep
))
2018 for (; cp
< ep
; cp
++) {
2020 /* in an escape sequence */
2022 /* at byte 1 of an escape sequence */
2023 mask
= hex2nib
[((unsigned char) *cp
) & 0x7f];
2024 /* next is byte 2 */
2027 /* at byte 2 of an escape sequence */
2029 mask
|= hex2nib
[((unsigned char) *cp
) & 0x7f];
2030 putc (mask
, ce
->ce_fp
);
2032 MD5Update (&mdContext
, &mask
, 1);
2033 if (ferror (ce
->ce_fp
)) {
2034 content_error (ce
->ce_file
, ct
, "error writing to");
2037 /* finished escape sequence; next may be literal or a new
2038 * escape sequence */
2041 /* on to next byte */
2045 /* not in an escape sequence */
2047 /* starting an escape sequence, or invalid '='? */
2048 if (cp
+ 1 < ep
&& cp
[1] == '\n') {
2049 /* "=\n" soft line break, eat the \n */
2053 if (cp
+ 1 >= ep
|| cp
+ 2 >= ep
) {
2054 /* We don't have 2 bytes left, so this is an invalid
2055 * escape sequence; just show the raw bytes (below). */
2056 } else if (isxdigit ((unsigned char) cp
[1]) &&
2057 isxdigit ((unsigned char) cp
[2])) {
2058 /* Next 2 bytes are hex digits, making this a valid escape
2059 * sequence; let's decode it (above). */
2063 /* One or both of the next 2 is out of range, making this
2064 * an invalid escape sequence; just show the raw bytes
2069 /* Just show the raw byte. */
2070 putc (*cp
, ce
->ce_fp
);
2073 MD5Update (&mdContext
, (unsigned char *) "\r\n",2);
2075 MD5Update (&mdContext
, (unsigned char *) cp
, 1);
2078 if (ferror (ce
->ce_fp
)) {
2079 content_error (ce
->ce_file
, ct
, "error writing to");
2085 content_error (NULL
, ct
,
2086 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2090 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2092 if (fflush (ce
->ce_fp
)) {
2093 content_error (ce
->ce_file
, ct
, "error writing to");
2098 unsigned char digest
[16];
2100 MD5Final (digest
, &mdContext
);
2101 if (memcmp((char *) digest
, (char *) ct
->c_digest
,
2102 sizeof(digest
) / sizeof(digest
[0])))
2103 content_error (NULL
, ct
,
2104 "content integrity suspect (digest mismatch) -- continuing");
2107 fprintf (stderr
, "content integrity confirmed\n");
2110 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2113 *file
= ce
->ce_file
;
2119 return fileno (ce
->ce_fp
);
2122 free_encoding (ct
, 0);
2139 if (init_encoding (ct
, open7Bit
) == NOTOK
)
2142 ct
->c_cesizefnx
= NULL
; /* no need to decode for real size */
2148 open7Bit (CT ct
, char **file
)
2150 int cc
, fd
, len
, own_ct_fp
= 0;
2151 char buffer
[BUFSIZ
];
2152 /* sbeck -- handle suffixes */
2155 CE ce
= &ct
->c_cefile
;
2158 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2163 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2164 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2170 if (*file
== NULL
) {
2173 ce
->ce_file
= add (*file
, NULL
);
2177 /* sbeck@cise.ufl.edu -- handle suffixes */
2179 if ((cp
= context_find_by_type ("suffix", ci
->ci_type
, ci
->ci_subtype
))) {
2180 if (ce
->ce_unlink
) {
2181 /* Create temporary file with filename extension. */
2182 if ((ce
->ce_file
= m_mktemps(invo_name
, cp
, NULL
, NULL
)) == NULL
) {
2183 adios(NULL
, "unable to create temporary file in %s",
2187 ce
->ce_file
= add (cp
, ce
->ce_file
);
2189 } else if (*file
== NULL
) {
2191 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2192 adios(NULL
, "unable to create temporary file in %s",
2195 ce
->ce_file
= add (tempfile
, NULL
);
2198 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2199 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2203 if (ct
->c_type
== CT_MULTIPART
) {
2204 CI ci
= &ct
->c_ctinfo
;
2208 fprintf (ce
->ce_fp
, "%s: %s/%s", TYPE_FIELD
, ci
->ci_type
, ci
->ci_subtype
);
2209 len
+= strlen (TYPE_FIELD
) + 2 + strlen (ci
->ci_type
)
2210 + 1 + strlen (ci
->ci_subtype
);
2211 buffer
= output_params(len
, ci
->ci_first_pm
, &len
, 0);
2214 fputs (buffer
, ce
->ce_fp
);
2218 if (ci
->ci_comment
) {
2219 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
2220 fputs ("\n\t", ce
->ce_fp
);
2224 putc (' ', ce
->ce_fp
);
2227 fprintf (ce
->ce_fp
, "(%s)", ci
->ci_comment
);
2230 fprintf (ce
->ce_fp
, "\n");
2232 fprintf (ce
->ce_fp
, "%s:%s", ID_FIELD
, ct
->c_id
);
2234 fprintf (ce
->ce_fp
, "%s:%s", DESCR_FIELD
, ct
->c_descr
);
2236 fprintf (ce
->ce_fp
, "%s:%s", DISPO_FIELD
, ct
->c_dispo
);
2237 fprintf (ce
->ce_fp
, "\n");
2240 if ((len
= ct
->c_end
- ct
->c_begin
) < 0)
2241 adios (NULL
, "internal error(3)");
2244 if ((ct
->c_fp
= fopen (ct
->c_file
, "r")) == NULL
) {
2245 content_error (ct
->c_file
, ct
, "unable to open for reading");
2251 lseek (fd
= fileno (ct
->c_fp
), (off_t
) ct
->c_begin
, SEEK_SET
);
2253 switch (cc
= read (fd
, buffer
, sizeof(buffer
) - 1)) {
2255 content_error (ct
->c_file
, ct
, "error reading from");
2259 content_error (NULL
, ct
, "premature eof");
2267 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, ce
->ce_fp
) < cc
) {
2268 advise ("open7Bit", "fwrite");
2270 if (ferror (ce
->ce_fp
)) {
2271 content_error (ce
->ce_file
, ct
, "error writing to");
2276 fseek (ct
->c_fp
, 0L, SEEK_SET
);
2278 if (fflush (ce
->ce_fp
)) {
2279 content_error (ce
->ce_file
, ct
, "error writing to");
2283 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2286 *file
= ce
->ce_file
;
2291 return fileno (ce
->ce_fp
);
2294 free_encoding (ct
, 0);
2308 openExternal (CT ct
, CT cb
, CE ce
, char **file
, int *fd
)
2310 char cachefile
[BUFSIZ
];
2313 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2318 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2319 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2325 if (find_cache (ct
, rcachesw
, (int *) 0, cb
->c_id
,
2326 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2327 if ((ce
->ce_fp
= fopen (cachefile
, "r"))) {
2328 ce
->ce_file
= getcpy (cachefile
);
2332 admonish (cachefile
, "unable to fopen for reading");
2336 *fd
= fileno (ce
->ce_fp
);
2340 *file
= ce
->ce_file
;
2341 *fd
= fileno (ce
->ce_fp
);
2352 return init_encoding (ct
, openFile
);
2357 openFile (CT ct
, char **file
)
2360 char cachefile
[BUFSIZ
];
2361 struct exbody
*e
= ct
->c_ctexbody
;
2362 CE ce
= &ct
->c_cefile
;
2364 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2376 content_error (NULL
, ct
, "missing name parameter");
2380 ce
->ce_file
= getcpy (e
->eb_name
);
2383 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "r")) == NULL
) {
2384 content_error (ce
->ce_file
, ct
, "unable to fopen for reading");
2388 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2389 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2390 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2394 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2395 if ((fp
= fopen (cachefile
, "w"))) {
2397 char buffer
[BUFSIZ
];
2398 FILE *gp
= ce
->ce_fp
;
2400 fseek (gp
, 0L, SEEK_SET
);
2402 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2404 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2405 advise ("openFile", "fwrite");
2410 admonish (ce
->ce_file
, "error reading");
2411 (void) m_unlink (cachefile
);
2415 admonish (cachefile
, "error writing");
2416 (void) m_unlink (cachefile
);
2423 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2424 *file
= ce
->ce_file
;
2425 return fileno (ce
->ce_fp
);
2435 return init_encoding (ct
, openFTP
);
2440 openFTP (CT ct
, char **file
)
2442 int cachetype
, caching
, fd
;
2444 char *bp
, *ftp
, *user
, *pass
;
2445 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2447 CE ce
= &ct
->c_cefile
;
2448 static char *username
= NULL
;
2449 static char *password
= NULL
;
2453 if ((ftp
= context_find (nmhaccessftp
)) && !*ftp
)
2459 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2470 if (!e
->eb_name
|| !e
->eb_site
) {
2471 content_error (NULL
, ct
, "missing %s parameter",
2472 e
->eb_name
? "site": "name");
2476 /* Get the buffer ready to go */
2478 buflen
= sizeof(buffer
);
2481 * Construct the query message for user
2483 snprintf (bp
, buflen
, "Retrieve %s", e
->eb_name
);
2489 snprintf (bp
, buflen
, " (content %s)", e
->eb_partno
);
2495 snprintf (bp
, buflen
, "\n using %sFTP from site %s",
2496 e
->eb_flags
? "anonymous " : "", e
->eb_site
);
2501 if (e
->eb_size
> 0) {
2502 snprintf (bp
, buflen
, " (%lu octets)", e
->eb_size
);
2507 snprintf (bp
, buflen
, "? ");
2510 * Now, check the answer
2512 if (!getanswer (buffer
))
2517 snprintf (buffer
, sizeof(buffer
), "%s@%s", getusername (),
2521 ruserpass (e
->eb_site
, &username
, &password
);
2526 ce
->ce_unlink
= (*file
== NULL
);
2528 cachefile
[0] = '\0';
2529 if ((!e
->eb_permission
|| strcasecmp (e
->eb_permission
, "read-write"))
2530 && find_cache (NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2531 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2532 if (*file
== NULL
) {
2539 ce
->ce_file
= add (*file
, NULL
);
2541 ce
->ce_file
= add (cachefile
, NULL
);
2544 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2545 adios(NULL
, "unable to create temporary file in %s",
2548 ce
->ce_file
= add (tempfile
, NULL
);
2551 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2552 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2557 int child_id
, i
, vecp
;
2561 vec
[vecp
++] = r1bindex (ftp
, '/');
2562 vec
[vecp
++] = e
->eb_site
;
2565 vec
[vecp
++] = e
->eb_dir
;
2566 vec
[vecp
++] = e
->eb_name
;
2567 vec
[vecp
++] = ce
->ce_file
,
2568 vec
[vecp
++] = e
->eb_mode
&& !strcasecmp (e
->eb_mode
, "ascii")
2569 ? "ascii" : "binary";
2574 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2578 adios ("fork", "unable to");
2582 close (fileno (ce
->ce_fp
));
2584 fprintf (stderr
, "unable to exec ");
2590 if (pidXwait (child_id
, NULL
)) {
2591 username
= password
= NULL
;
2601 chmod (cachefile
, cachetype
? m_gmprot () : 0444);
2606 mask
= umask (cachetype
? ~m_gmprot () : 0222);
2607 if ((fp
= fopen (cachefile
, "w"))) {
2609 FILE *gp
= ce
->ce_fp
;
2611 fseek (gp
, 0L, SEEK_SET
);
2613 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), gp
))
2615 if ((int) fwrite (buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2616 advise ("openFTP", "fwrite");
2621 admonish (ce
->ce_file
, "error reading");
2622 (void) m_unlink (cachefile
);
2626 admonish (cachefile
, "error writing");
2627 (void) m_unlink (cachefile
);
2635 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2636 *file
= ce
->ce_file
;
2637 return fileno (ce
->ce_fp
);
2648 return init_encoding (ct
, openMail
);
2653 openMail (CT ct
, char **file
)
2655 int child_id
, fd
, i
, vecp
;
2657 char *bp
, buffer
[BUFSIZ
], *vec
[7];
2658 struct exbody
*e
= ct
->c_ctexbody
;
2659 CE ce
= &ct
->c_cefile
;
2661 switch (openExternal (e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2672 if (!e
->eb_server
) {
2673 content_error (NULL
, ct
, "missing server parameter");
2677 /* Get buffer ready to go */
2679 buflen
= sizeof(buffer
);
2681 /* Now, construct query message */
2682 snprintf (bp
, buflen
, "Retrieve content");
2688 snprintf (bp
, buflen
, " %s", e
->eb_partno
);
2694 snprintf (bp
, buflen
, " by asking %s\n\n%s\n? ",
2696 e
->eb_subject
? e
->eb_subject
: e
->eb_body
);
2698 /* Now, check answer */
2699 if (!getanswer (buffer
))
2703 vec
[vecp
++] = r1bindex (mailproc
, '/');
2704 vec
[vecp
++] = e
->eb_server
;
2705 vec
[vecp
++] = "-subject";
2706 vec
[vecp
++] = e
->eb_subject
? e
->eb_subject
: "mail-server request";
2707 vec
[vecp
++] = "-body";
2708 vec
[vecp
++] = e
->eb_body
;
2711 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
< 5; i
++)
2715 advise ("fork", "unable to");
2719 execvp (mailproc
, vec
);
2720 fprintf (stderr
, "unable to exec ");
2726 if (pidXwait (child_id
, NULL
) == OK
)
2727 advise (NULL
, "request sent");
2731 if (*file
== NULL
) {
2733 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2734 adios(NULL
, "unable to create temporary file in %s",
2737 ce
->ce_file
= add (tempfile
, NULL
);
2740 ce
->ce_file
= add (*file
, NULL
);
2744 if ((ce
->ce_fp
= fopen (ce
->ce_file
, "w+")) == NULL
) {
2745 content_error (ce
->ce_file
, ct
, "unable to fopen for reading/writing");
2749 /* showproc is for mhshow and mhstore, though mhlist -debug
2750 * prints it, too. */
2752 free (ct
->c_showproc
);
2753 ct
->c_showproc
= add ("true", NULL
);
2755 fseek (ce
->ce_fp
, 0L, SEEK_SET
);
2756 *file
= ce
->ce_file
;
2757 return fileno (ce
->ce_fp
);
2768 return init_encoding (ct
, openURL
);
2773 openURL (CT ct
, char **file
)
2775 struct exbody
*e
= ct
->c_ctexbody
;
2776 CE ce
= &ct
->c_cefile
;
2777 char *urlprog
, *program
;
2778 char buffer
[BUFSIZ
], cachefile
[BUFSIZ
];
2779 int fd
, caching
, cachetype
;
2780 struct msgs_array args
= { 0, 0, NULL
};
2783 if ((urlprog
= context_find(nmhaccessurl
)) && *urlprog
== '\0')
2787 content_error(NULL
, ct
, "No entry for nmh-access-url in profile");
2791 switch (openExternal(e
->eb_parent
, e
->eb_content
, ce
, file
, &fd
)) {
2803 content_error(NULL
, ct
, "missing url parameter");
2807 ce
->ce_unlink
= (*file
== NULL
);
2809 cachefile
[0] = '\0';
2811 if (find_cache(NULL
, wcachesw
, &cachetype
, e
->eb_content
->c_id
,
2812 cachefile
, sizeof(cachefile
)) != NOTOK
) {
2813 if (*file
== NULL
) {
2820 ce
->ce_file
= add(*file
, NULL
);
2822 ce
->ce_file
= add(cachefile
, NULL
);
2825 if ((tempfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
)) == NULL
) {
2826 adios(NULL
, "unable to create temporary file in %s",
2829 ce
->ce_file
= add (tempfile
, NULL
);
2832 if ((ce
->ce_fp
= fopen(ce
->ce_file
, "w+")) == NULL
) {
2833 content_error(ce
->ce_file
, ct
, "unable to fopen for read/writing");
2837 switch (child_id
= fork()) {
2839 adios ("fork", "unable to");
2843 argsplit_msgarg(&args
, urlprog
, &program
);
2844 app_msgarg(&args
, e
->eb_url
);
2845 app_msgarg(&args
, NULL
);
2846 dup2(fileno(ce
->ce_fp
), 1);
2847 close(fileno(ce
->ce_fp
));
2848 execvp(program
, args
.msgs
);
2849 fprintf(stderr
, "Unable to exec ");
2855 if (pidXwait(child_id
, NULL
)) {
2863 chmod(cachefile
, cachetype
? m_gmprot() : 0444);
2868 mask
= umask (cachetype
? ~m_gmprot() : 0222);
2869 if ((fp
= fopen(cachefile
, "w"))) {
2871 FILE *gp
= ce
->ce_fp
;
2873 fseeko(gp
, 0, SEEK_SET
);
2875 while ((cc
= fread(buffer
, sizeof(*buffer
),
2876 sizeof(buffer
), gp
)) > 0)
2877 if ((int) fwrite(buffer
, sizeof(*buffer
), cc
, fp
) < cc
) {
2878 advise ("openURL", "fwrite");
2884 admonish(ce
->ce_file
, "error reading");
2885 (void) m_unlink (cachefile
);
2892 fseeko(ce
->ce_fp
, 0, SEEK_SET
);
2893 *file
= ce
->ce_file
;
2898 readDigest (CT ct
, char *cp
)
2903 unsigned char *dp
, value
, *ep
;
2909 for (ep
= (dp
= ct
->c_digest
)
2910 + sizeof(ct
->c_digest
) / sizeof(ct
->c_digest
[0]); *cp
; cp
++)
2915 || (value
= b642nib
[*cp
& 0x7f]) > 0x3f) {
2917 fprintf (stderr
, "invalid BASE64 encoding\n");
2921 bits
|= value
<< bitno
;
2923 if ((bitno
-= 6) < 0) {
2924 if (dp
+ (3 - skip
) > ep
)
2925 goto invalid_digest
;
2926 *dp
++ = (bits
>> 16) & 0xff;
2928 *dp
++ = (bits
>> 8) & 0xff;
2930 *dp
++ = bits
& 0xff;
2940 goto self_delimiting
;
2945 fprintf (stderr
, "premature ending (bitno %d)\n", bitno
);
2955 fprintf (stderr
, "invalid MD5 digest (got %d octets)\n",
2963 fprintf (stderr
, "MD5 digest=");
2964 for (dp
= ct
->c_digest
; dp
< ep
; dp
++)
2965 fprintf (stderr
, "%02x", *dp
& 0xff);
2966 fprintf (stderr
, "\n");
2973 /* Multipart parts might have content before the first subpart and/or
2974 after the last subpart that hasn't been stored anywhere else, so do
2977 get_leftover_mp_content (CT ct
, int before
/* or after */)
2979 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
2981 int found_boundary
= 0;
2987 char *content
= NULL
;
2989 if (! m
) return NOTOK
;
2992 if (! m
->mp_parts
|| ! m
->mp_parts
->mp_part
) return NOTOK
;
2994 /* Isolate the beginning of this part to the beginning of the
2995 first subpart and save any content between them. */
2996 fseeko (ct
->c_fp
, ct
->c_begin
, SEEK_SET
);
2997 max
= m
->mp_parts
->mp_part
->c_begin
- ct
->c_begin
;
2998 boundary
= concat ("--", m
->mp_start
, NULL
);
3000 struct part
*last_subpart
= NULL
;
3001 struct part
*subpart
;
3003 /* Go to the last subpart to get its end position. */
3004 for (subpart
= m
->mp_parts
; subpart
; subpart
= subpart
->mp_next
) {
3005 last_subpart
= subpart
;
3008 if (last_subpart
== NULL
) return NOTOK
;
3010 /* Isolate the end of the last subpart to the end of this part
3011 and save any content between them. */
3012 fseeko (ct
->c_fp
, last_subpart
->mp_part
->c_end
, SEEK_SET
);
3013 max
= ct
->c_end
- last_subpart
->mp_part
->c_end
;
3014 boundary
= concat ("--", m
->mp_stop
, NULL
);
3017 /* Back up by 1 to pick up the newline. */
3018 while ((gotlen
= getline(&bufp
, &buflen
, ct
->c_fp
)) != -1) {
3020 /* Don't look beyond beginning of first subpart (before) or
3021 next part (after). */
3022 if (read
> max
) bufp
[read
-max
] = '\0';
3025 if (! strcmp (bufp
, boundary
)) {
3029 if (! found_boundary
&& ! strcmp (bufp
, boundary
)) {
3035 if ((before
&& ! found_boundary
) || (! before
&& found_boundary
)) {
3037 char *old_content
= content
;
3038 content
= concat (content
, bufp
, NULL
);
3042 ? concat ("\n", bufp
, NULL
)
3043 : concat (bufp
, NULL
);
3048 if (found_boundary
|| read
> max
) break;
3050 if (read
> max
) break;
3054 /* Skip the newline if that's all there is. */
3058 /* Remove trailing newline, except at EOF. */
3059 if ((before
|| ! feof (ct
->c_fp
)) &&
3060 (cp
= content
+ strlen (content
)) > content
&&
3065 if (strlen (content
) > 1) {
3067 m
->mp_content_before
= content
;
3069 m
->mp_content_after
= content
;
3084 ct_type_str (int type
) {
3086 case CT_APPLICATION
:
3087 return "application";
3103 return "unknown_type";
3109 ct_subtype_str (int type
, int subtype
) {
3111 case CT_APPLICATION
:
3113 case APPLICATION_OCTETS
:
3115 case APPLICATION_POSTSCRIPT
:
3116 return "postscript";
3118 return "unknown_app_subtype";
3122 case MESSAGE_RFC822
:
3124 case MESSAGE_PARTIAL
:
3126 case MESSAGE_EXTERNAL
:
3129 return "unknown_msg_subtype";
3135 case MULTI_ALTERNATE
:
3136 return "alternative";
3139 case MULTI_PARALLEL
:
3144 return "unknown_multipart_subtype";
3155 return "unknown_text_subtype";
3158 return "unknown_type";
3164 ct_str_type (const char *type
) {
3165 struct str2init
*s2i
;
3167 for (s2i
= str2cts
; s2i
->si_key
; ++s2i
) {
3168 if (! strcasecmp (type
, s2i
->si_key
)) {
3172 if (! s2i
->si_key
&& ! uprf (type
, "X-")) {
3181 ct_str_subtype (int type
, const char *subtype
) {
3185 case CT_APPLICATION
:
3186 for (kv
= SubApplication
; kv
->kv_key
; ++kv
) {
3187 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3191 return kv
->kv_value
;
3193 for (kv
= SubMessage
; kv
->kv_key
; ++kv
) {
3194 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3198 return kv
->kv_value
;
3200 for (kv
= SubMultiPart
; kv
->kv_key
; ++kv
) {
3201 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3205 return kv
->kv_value
;
3207 for (kv
= SubText
; kv
->kv_key
; ++kv
) {
3208 if (! strcasecmp (subtype
, kv
->kv_key
)) {
3212 return kv
->kv_value
;
3219 /* Find the content type and InitFunc for the CT. */
3220 const struct str2init
*
3221 get_ct_init (int type
) {
3222 const struct str2init
*sp
;
3224 for (sp
= str2cts
; sp
->si_key
; ++sp
) {
3225 if (type
== sp
->si_val
) {
3234 ce_str (int encoding
) {
3239 return "quoted-printable";
3255 /* Find the content type and InitFunc for the content encoding method. */
3256 const struct str2init
*
3257 get_ce_method (const char *method
) {
3258 struct str2init
*sp
;
3260 for (sp
= str2ces
; sp
->si_key
; ++sp
) {
3261 if (! strcasecmp (method
, sp
->si_key
)) {
3270 * Parse a series of MIME attributes (or parameters) given a header as
3273 * Arguments include:
3275 * filename - Name of input file (for error messages)
3276 * fieldname - Name of field being processed
3277 * headerp - Pointer to pointer of the beginning of the MIME attributes.
3278 * Updated to point to end of attributes when finished.
3279 * param_head - Pointer to head of parameter list
3280 * param_tail - Pointer to tail of parameter list
3281 * commentp - Pointer to header comment pointer (may be NULL)
3283 * Returns OK if parsing was successful, NOTOK if parsing failed, and
3284 * DONE to indicate a benign error (minor parsing error, but the program
3289 parse_header_attrs (const char *filename
, const char *fieldname
,
3290 char **header_attrp
, PM
*param_head
, PM
*param_tail
,
3293 char *cp
= *header_attrp
;
3299 struct sectlist
*next
;
3305 struct sectlist
*sechead
;
3306 struct parmlist
*next
;
3307 } *pp
, *pp2
, *phead
= NULL
;
3309 while (*cp
== ';') {
3310 char *dp
, *vp
, *up
, *nameptr
, *valptr
, *charset
= NULL
, *lang
= NULL
;
3311 int encoded
= 0, partial
= 0, len
= 0, index
= 0;
3314 while (isspace ((unsigned char) *cp
))
3318 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3323 if (! suppress_extraneous_trailing_semicolon_warning
) {
3325 "extraneous trailing ';' in message %s's %s: "
3327 filename
, fieldname
);
3329 extraneous_trailing_semicolon
= 1;
3333 /* down case the attribute name */
3334 for (dp
= cp
; istoken ((unsigned char) *dp
); dp
++)
3335 if (isalpha((unsigned char) *dp
) && isupper ((unsigned char) *dp
))
3336 *dp
= tolower ((unsigned char) *dp
);
3338 for (up
= dp
; isspace ((unsigned char) *dp
);)
3340 if (dp
== cp
|| *dp
!= '=') {
3342 "invalid parameter in message %s's %s: "
3343 "field\n%*sparameter %s (error detected at offset %d)",
3344 filename
, fieldname
, strlen(invo_name
) + 2, "",cp
, dp
- cp
);
3349 * To handle RFC 2231, we have to deal with the following extensions:
3351 * name*=encoded-value
3352 * name*<N>=part-N-of-a-parameter-value
3353 * name*<N>*=encoded-part-N-of-a-parameter-value
3356 * If there's a * right before the equal sign, it's encoded.
3357 * If there's a * and one or more digits, then it's section N.
3359 * Remember we can have one or the other, or both. cp points to
3360 * beginning of name, up points past the last character in the
3364 for (vp
= cp
; vp
< up
; vp
++) {
3365 if (*vp
== '*' && vp
< up
- 1) {
3368 } else if (*vp
== '*' && vp
== up
- 1) {
3370 } else if (partial
) {
3371 if (isdigit((unsigned char) *vp
))
3372 index
= *vp
- '0' + index
* 10;
3374 advise (NULL
, "invalid parameter index in message %s's "
3375 "%s: field\n%*s(parameter %s)", filename
,
3376 fieldname
, strlen(invo_name
) + 2, "", cp
);
3385 * Break out the parameter name and value sections and allocate
3389 nameptr
= mh_xmalloc(len
+ 1);
3390 strncpy(nameptr
, cp
, len
);
3391 nameptr
[len
] = '\0';
3393 for (dp
++; isspace ((unsigned char) *dp
);)
3398 * Single quotes delimit the character set and language tag.
3399 * They are required on the first section (or a complete
3404 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3410 charset
= mh_xmalloc(len
+ 1);
3411 strncpy(charset
, dp
, len
);
3412 charset
[len
] = '\0';
3418 advise(NULL
, "missing charset in message %s's %s: "
3419 "field\n%*s(parameter %s)", filename
, fieldname
,
3420 strlen(invo_name
) + 2, "", nameptr
);
3426 while (*vp
!= '\'' && !isspace((unsigned char) *vp
) &&
3433 lang
= mh_xmalloc(len
+ 1);
3434 strncpy(lang
, dp
, len
);
3441 advise(NULL
, "missing language tag in message %s's %s: "
3442 "field\n%*s(parameter %s)", filename
, fieldname
,
3443 strlen(invo_name
) + 2, "", nameptr
);
3454 * At this point vp should be pointing at the beginning
3455 * of the encoded value/section. Continue until we reach
3456 * the end or get whitespace. But first, calculate the
3457 * length so we can allocate the correct buffer size.
3460 for (vp
= dp
, len
= 0; istoken(*vp
); vp
++) {
3462 if (*(vp
+ 1) == '\0' ||
3463 !isxdigit((unsigned char) *(vp
+ 1)) ||
3464 *(vp
+ 2) == '\0' ||
3465 !isxdigit((unsigned char) *(vp
+ 2))) {
3466 advise(NULL
, "invalid encoded sequence in message "
3467 "%s's %s: field\n%*s(parameter %s)",
3468 filename
, fieldname
, strlen(invo_name
) + 2,
3482 up
= valptr
= mh_xmalloc(len
+ 1);
3484 for (vp
= dp
; istoken(*vp
); vp
++) {
3486 *up
++ = decode_qp(*(vp
+ 1), *(vp
+ 2));
3497 * A "normal" string. If it's got a leading quote, then we
3498 * strip the quotes out. Otherwise go until we reach the end
3499 * or get whitespace. Note we scan it twice; once to get the
3500 * length, then the second time copies it into the destination
3507 for (cp
= dp
+ 1;;) {
3512 "invalid quoted-string in message %s's %s: "
3513 "field\n%*s(parameter %s)",
3514 filename
, fieldname
, strlen(invo_name
) + 2, "",
3537 for (cp
= dp
; istoken (*cp
); cp
++) {
3542 valptr
= mh_xmalloc(len
+ 1);
3546 for (cp
= dp
+ 1, vp
= valptr
, i
= 0; i
< len
; i
++) {
3554 strncpy(valptr
, cp
= dp
, len
);
3562 * If 'partial' is set, we don't allocate a parameter now. We
3563 * put it on the parameter linked list to be reassembled later.
3565 * "phead" points to a list of all parameters we need to reassemble.
3566 * Each parameter has a list of sections. We insert the sections in
3571 for (pp
= phead
; pp
!= NULL
; pp
= pp
->next
) {
3572 if (strcasecmp(nameptr
, pp
->name
) == 0)
3577 pp
= mh_xmalloc(sizeof(*pp
));
3578 memset(pp
, 0, sizeof(*pp
));
3585 * Insert this into the section linked list
3588 sp
= mh_xmalloc(sizeof(*sp
));
3589 memset(sp
, 0, sizeof(*sp
));
3594 if (pp
->sechead
== NULL
|| pp
->sechead
->index
> index
) {
3595 sp
->next
= pp
->sechead
;
3598 for (sp2
= pp
->sechead
; sp2
!= NULL
; sp2
= sp2
->next
) {
3599 if (sp2
->index
== sp
->index
) {
3600 advise (NULL
, "duplicate index (%d) in message "
3601 "%s's %s: field\n%*s(parameter %s)", sp
->index
,
3602 filename
, fieldname
, strlen(invo_name
) + 2, "",
3607 if (sp2
->index
< sp
->index
&&
3608 (sp2
->next
== NULL
|| sp2
->next
->index
> sp
->index
)) {
3609 sp
->next
= sp2
->next
;
3616 advise(NULL
, "Internal error: cannot insert partial "
3617 "param in message %s's %s: field\n%*s(parameter %s)",
3618 filename
, fieldname
, strlen(invo_name
) + 2, "",
3626 * Save our charset and lang tags.
3629 if (index
== 0 && encoded
) {
3632 pp
->charset
= charset
;
3638 pm
= add_param(param_head
, param_tail
, nameptr
, valptr
, 1);
3639 pm
->pm_charset
= charset
;
3643 while (isspace ((unsigned char) *cp
))
3647 get_comment (filename
, fieldname
, &cp
, commentp
) == NOTOK
) {
3653 * Now that we're done, reassemble all of the partial parameters.
3656 for (pp
= phead
; pp
!= NULL
; ) {
3660 for (sp
= pp
->sechead
; sp
!= NULL
; sp
= sp
->next
) {
3661 if (sp
->index
!= pindex
++) {
3662 advise(NULL
, "missing section %d for parameter in "
3663 "message %s's %s: field\n%*s(parameter %s)", pindex
- 1,
3664 filename
, fieldname
, strlen(invo_name
) + 2, "",
3671 p
= q
= mh_xmalloc(tlen
+ 1);
3672 for (sp
= pp
->sechead
; sp
!= NULL
; ) {
3673 memcpy(q
, sp
->value
, sp
->len
);
3683 pm
= add_param(param_head
, param_tail
, pp
->name
, p
, 1);
3684 pm
->pm_charset
= pp
->charset
;
3685 pm
->pm_lang
= pp
->lang
;
3696 * Return the charset for a particular content type.
3700 content_charset (CT ct
) {
3701 char *ret_charset
= NULL
;
3703 ret_charset
= get_param(ct
->c_ctinfo
.ci_first_pm
, "charset", '?', 0);
3705 return ret_charset
? ret_charset
: getcpy ("US-ASCII");
3710 * Create a string based on a list of output parameters. Assume that this
3711 * parameter string will be appended to an existing header, so start out
3712 * with the separator (;). Perform RFC 2231 encoding when necessary.
3716 output_params(size_t initialwidth
, PM params
, int *offsetout
, int external
)
3718 char *paramout
= NULL
;
3719 char line
[CPERLIN
* 2], *q
;
3720 int curlen
, index
, cont
, encode
, i
;
3721 size_t valoff
, numchars
;
3723 while (params
!= NULL
) {
3729 if (external
&& strcasecmp(params
->pm_name
, "body") == 0)
3732 if (strlen(params
->pm_name
) > CPERLIN
) {
3733 advise(NULL
, "Parameter name \"%s\" is too long", params
->pm_name
);
3739 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
, &numchars
);
3742 * Loop until we get a parameter that fits within a line. We
3743 * assume new lines start with a tab, so check our overflow based
3753 * At this point we're definitely continuing the line, so
3754 * be sure to include the parameter name and section index.
3757 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3758 params
->pm_name
, index
);
3761 * Both of these functions do a NUL termination
3765 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3766 numchars
, valoff
, index
);
3768 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3779 curlen
= param_len(params
, index
, valoff
, &encode
, &cont
,
3784 * "line" starts with a ;\n\t, so that doesn't count against
3785 * the length. But add 8 since it starts with a tab; that's
3786 * how we end up with 5.
3789 initialwidth
= strlen(line
) + 5;
3792 * At this point the line should be built, so add it to our
3793 * current output buffer.
3796 paramout
= add(line
, paramout
);
3800 * If this won't fit on the line, start a new one. Save room in
3801 * case we need a semicolon on the end
3804 if (initialwidth
+ curlen
> CPERLIN
- 1) {
3816 * At this point, we're either finishing a contined parameter, or
3817 * we're working on a new one.
3821 q
+= snprintf(q
, sizeof(line
) - (q
- line
), "%s*%d",
3822 params
->pm_name
, index
);
3824 strncpy(q
, params
->pm_name
, sizeof(line
) - (q
- line
));
3829 i
= encode_param(params
, q
, sizeof(line
) - (q
- line
),
3830 strlen(params
->pm_value
+ valoff
), valoff
, index
);
3832 i
= normal_param(params
, q
, sizeof(line
) - (q
- line
),
3833 strlen(params
->pm_value
+ valoff
), valoff
);
3841 paramout
= add(line
, paramout
);
3842 initialwidth
+= strlen(line
);
3844 params
= params
->pm_next
;
3848 *offsetout
= initialwidth
;
3854 * Calculate the size of a parameter.
3858 * pm - The parameter being output
3859 * index - If continuing the parameter, the index of the section
3861 * valueoff - The current offset into the parameter value that we're
3862 * working on (previous sections have consumed valueoff bytes).
3863 * encode - Set if we should perform encoding on this parameter section
3864 * (given that we're consuming bytesfit bytes).
3865 * cont - Set if the remaining data in value will not fit on a single
3866 * line and will need to be continued.
3867 * bytesfit - The number of bytes that we can consume from the parameter
3868 * value and still fit on a completely new line. The
3869 * calculation assumes the new line starts with a tab,
3870 * includes the parameter name and any encoding, and fits
3871 * within CPERLIN bytes. Will always be at least 1.
3875 param_len(PM pm
, int index
, size_t valueoff
, int *encode
, int *cont
,
3878 char *start
= pm
->pm_value
+ valueoff
, *p
, indexchar
[32];
3879 size_t len
= 0, fit
= 0;
3880 int fitlimit
= 0, eightbit
, maxfit
;
3885 * Add up the length. First, start with the parameter name.
3888 len
= strlen(pm
->pm_name
);
3891 * Scan the parameter value and see if we need to do encoding for this
3895 eightbit
= contains8bit(start
, NULL
);
3898 * Determine if we need to encode this section. Encoding is necessary if:
3900 * - There are any 8-bit characters at all and we're on the first
3902 * - There are 8-bit characters within N bytes of our section start.
3903 * N is calculated based on the number of bytes it would take to
3904 * reach CPERLIN. Specifically:
3905 * 8 (starting tab) +
3906 * strlen(param name) +
3907 * 4 ('* for section marker, '=', opening/closing '"')
3909 * is the number of bytes used by everything that isn't part of the
3910 * value. So that gets subtracted from CPERLIN.
3913 snprintf(indexchar
, sizeof(indexchar
), "%d", index
);
3914 maxfit
= CPERLIN
- (12 + len
+ strlen(indexchar
));
3915 if ((eightbit
&& index
== 0) || contains8bit(start
, start
+ maxfit
)) {
3919 len
++; /* Add in equal sign */
3923 * We're using maxfit as a marker for how many characters we can
3924 * fit into the line. Bump it by two because we're not using quotes
3931 * If we don't have a charset or language tag in this parameter,
3935 if (! pm
->pm_charset
) {
3936 pm
->pm_charset
= getcpy(write_charset_8bit());
3937 if (strcasecmp(pm
->pm_charset
, "US-ASCII") == 0)
3938 adios(NULL
, "8-bit characters in parameter \"%s\", but "
3939 "local character set is US-ASCII", pm
->pm_name
);
3942 pm
->pm_lang
= getcpy(NULL
); /* Default to a blank lang tag */
3944 len
++; /* For the encoding marker */
3947 int enclen
= strlen(pm
->pm_charset
) + strlen(pm
->pm_lang
) + 2;
3952 * We know we definitely need to include an index. maxfit already
3953 * includes the section marker.
3955 len
+= strlen(indexchar
);
3957 for (p
= start
; *p
!= '\0'; p
++) {
3958 if (isparamencode(*p
)) {
3966 * Just so there's no confusion: maxfit is counting OUTPUT
3967 * characters (post-encoding). fit is counting INPUT characters.
3969 if (! fitlimit
&& maxfit
>= 0)
3971 else if (! fitlimit
)
3976 * Calculate the string length, but add room for quoting \
3977 * and " if necessary. Also account for quotes at beginning
3980 for (p
= start
; *p
!= '\0'; p
++) {
3991 if (! fitlimit
&& maxfit
>= 0)
3993 else if (! fitlimit
)
4010 * Output an encoded parameter string.
4014 encode_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4015 size_t valueoff
, int index
)
4017 size_t outlen
= 0, n
;
4018 char *endptr
= output
+ len
, *p
;
4021 * First, output the marker for an encoded string.
4029 * If the index is 0, output the character set and language tag.
4030 * If theses were NULL, they should have already been filled in
4035 n
= snprintf(output
, len
- outlen
, "%s'%s'", pm
->pm_charset
,
4039 if (output
> endptr
) {
4040 advise(NULL
, "Internal error: parameter buffer overflow");
4046 * Copy over the value, encoding if necessary
4049 p
= pm
->pm_value
+ valueoff
;
4050 while (valuelen
-- > 0) {
4051 if (isparamencode(*p
)) {
4052 n
= snprintf(output
, len
- outlen
, "%%%02X", (unsigned char) *p
++);
4059 if (output
> endptr
) {
4060 advise(NULL
, "Internal error: parameter buffer overflow");
4071 * Output a "normal" parameter, without encoding. Be sure to escape
4072 * quotes and backslashes if necessary.
4076 normal_param(PM pm
, char *output
, size_t len
, size_t valuelen
,
4080 char *endptr
= output
+ len
, *p
;
4086 p
= pm
->pm_value
+ valueoff
;
4088 while (valuelen
-- > 0) {
4098 if (output
> endptr
) {
4099 advise(NULL
, "Internal error: parameter buffer overflow");
4104 if (output
- 2 > endptr
) {
4105 advise(NULL
, "Internal error: parameter buffer overflow");
4116 * Add a parameter to the parameter linked list
4120 add_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4122 PM pm
= mh_xmalloc(sizeof(*pm
));
4124 memset(pm
, 0, sizeof(*pm
));
4126 pm
->pm_name
= nocopy
? name
: getcpy(name
);
4127 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4130 (*last
)->pm_next
= pm
;
4141 * Either replace a current parameter with a new value, or add the parameter
4142 * to the parameter linked list.
4146 replace_param(PM
*first
, PM
*last
, char *name
, char *value
, int nocopy
)
4150 for (pm
= *first
; pm
!= NULL
; pm
= pm
->pm_next
) {
4151 if (strcasecmp(name
, pm
->pm_name
) == 0) {
4153 * If nocopy is set, it's assumed that we own both name
4154 * and value. We don't need name, so we discard it now.
4159 pm
->pm_value
= nocopy
? value
: getcpy(value
);
4164 return add_param(first
, last
, name
, value
, nocopy
);
4168 * Retrieve a parameter value from a parameter linked list. If the parameter
4169 * value needs converted to the local character set, do that now.
4173 get_param(PM first
, const char *name
, char replace
, int fetchonly
)
4175 while (first
!= NULL
) {
4176 if (strcasecmp(name
, first
->pm_name
) == 0) {
4178 return first
->pm_value
;
4180 return getcpy(get_param_value(first
, replace
));
4182 first
= first
->pm_next
;
4189 * Return a parameter value, converting to the local character set if
4193 char *get_param_value(PM pm
, char replace
)
4195 static char buffer
[4096]; /* I hope no parameters are larger */
4196 size_t bufsize
= sizeof(buffer
);
4201 ICONV_CONST
char *p
;
4202 #else /* HAVE_ICONV */
4204 #endif /* HAVE_ICONV */
4209 * If we don't have a character set indicated, it's assumed to be
4210 * US-ASCII. If it matches our character set, we don't need to convert
4214 if (!pm
->pm_charset
|| check_charset(pm
->pm_charset
,
4215 strlen(pm
->pm_charset
))) {
4216 return pm
->pm_value
;
4220 * In this case, we need to convert. If we have iconv support, use
4221 * that. Otherwise, go through and simply replace every non-ASCII
4222 * character with the substitution character.
4227 bufsize
= sizeof(buffer
);
4228 utf8
= strcasecmp(pm
->pm_charset
, "UTF-8") == 0;
4230 cd
= iconv_open(get_charset(), pm
->pm_charset
);
4231 if (cd
== (iconv_t
) -1) {
4235 inbytes
= strlen(pm
->pm_value
);
4239 if (iconv(cd
, &p
, &inbytes
, &q
, &bufsize
) == (size_t)-1) {
4240 if (errno
!= EILSEQ
) {
4245 * Reset shift state, substitute our character,
4246 * try to restart conversion.
4249 iconv(cd
, NULL
, NULL
, &q
, &bufsize
);
4262 for (++p
, --inbytes
;
4263 inbytes
> 0 && (((unsigned char) *q
) & 0xc0) == 0x80;
4282 #endif /* HAVE_ICONV */
4285 * Take everything non-ASCII and substituite the replacement character
4289 bufsize
= sizeof(buffer
);
4290 for (p
= pm
->pm_value
; *p
!= '\0' && bufsize
> 1; p
++, q
++, bufsize
--) {
4291 if (isascii((unsigned char) *p
) && !iscntrl((unsigned char) *p
))