2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
9 * Gunnar Ritter. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by Gunnar Ritter
22 * and his contributors.
23 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46 #endif /* HAVE_WCTYPE_H */
49 * Mail -- a mail program
51 * MIME support functions.
55 * You won't guess what these are for.
57 static const char basetable
[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
58 static char *mimetypes_world
= "/etc/mime.types";
59 static char *mimetypes_user
= "~/.mime.types";
60 char *us_ascii
= "us-ascii";
62 static int mustquote_body(int c
);
63 static int mustquote_hdr(const char *cp
, int wordstart
, int wordend
);
64 static int mustquote_inhdrq(int c
);
65 static size_t delctrl(char *cp
, size_t sz
);
66 static char *getcharset(int isclean
);
67 static int has_highbit(register const char *s
);
69 static void uppercopy(char *dest
, const char *src
);
70 static void stripdash(char *p
);
71 static void invalid_seq(int c
);
72 #endif /* HAVE_ICONV */
73 static int is_this_enc(const char *line
, const char *encoding
);
74 static char *mime_tline(char *x
, char *l
);
75 static char *mime_type(char *ext
, char *filename
);
76 static enum mimeclean
mime_isclean(FILE *f
);
77 static enum conversion
gettextconversion(void);
78 static char *ctohex(int c
, char *hex
);
79 static size_t mime_write_toqp(struct str
*in
, FILE *fo
, int (*mustquote
)(int));
80 static void mime_str_toqp(struct str
*in
, struct str
*out
,
81 int (*mustquote
)(int), int inhdr
);
82 static void mime_fromqp(struct str
*in
, struct str
*out
, int ishdr
);
83 static size_t mime_write_tohdr(struct str
*in
, FILE *fo
);
84 static size_t convhdra(char *str
, size_t len
, FILE *fp
);
85 static size_t mime_write_tohdr_a(struct str
*in
, FILE *f
);
86 static void addstr(char **buf
, size_t *sz
, size_t *pos
, char *str
, size_t len
);
87 static void addconv(char **buf
, size_t *sz
, size_t *pos
, char *str
, size_t len
);
88 static size_t fwrite_td(void *ptr
, size_t size
, size_t nmemb
, FILE *f
,
89 enum tdflags flags
, char *prefix
, size_t prefixlen
);
92 * Check if c must be quoted inside a message's body.
97 if (c
!= '\n' && (c
< 040 || c
== '=' || c
>= 0177))
103 * Check if c must be quoted inside a message's header.
106 mustquote_hdr(const char *cp
, int wordstart
, int wordend
)
110 if (c
!= '\n' && (c
< 040 || c
>= 0177))
112 if (wordstart
&& cp
[0] == '=' && cp
[1] == '?')
114 if (cp
[0] == '?' && cp
[1] == '=' &&
115 (wordend
|| cp
[2] == '\0' || whitechar(cp
[2]&0377)))
121 * Check if c must be quoted inside a quoting in a message's header.
124 mustquote_inhdrq(int c
)
127 && (c
<= 040 || c
== '=' || c
== '?' || c
== '_' || c
>= 0177))
133 delctrl(char *cp
, size_t sz
)
138 if (!cntrlchar(cp
[x
]&0377))
146 * Check if a name's address part contains invalid characters.
149 mime_name_invalid(struct name
*np
, int putmsg
)
151 char *name
= np
->n_name
, *addr
, *p
;
152 int in_quote
= 0, in_domain
= 0, err
= 0, hadat
= 0;
154 if (np
->n_flags
& NAME_MIME_CHECKED
)
155 return (np
->n_flags
& NAME_MIME_INVALID
) != 0;
156 np
->n_flags
|= NAME_MIME_CHECKED
;
158 if (is_fileaddr(name
))
162 if (addr
== NULL
|| *addr
== '\0') {
163 np
->n_flags
|= NAME_MIME_INVALID
;
166 for (p
= addr
; *p
!= '\0'; p
++) {
168 in_quote
= !in_quote
;
169 } else if (*p
< 040 || (*p
& 0377) >= 0177) {
172 } else if (in_domain
== 2) {
173 if ((*p
== ']' && p
[1] != '\0') || *p
== '\0'
174 || *p
== '\\' || whitechar(*p
& 0377)) {
178 } else if (in_quote
&& in_domain
== 0) {
180 } else if (*p
== '\\' && p
[1] != '\0') {
182 } else if (*p
== '@') {
185 fprintf(stderr
, catgets(catd
, CATSET
,
187 "%s contains invalid @@ sequence\n"),
199 } else if (*p
== '(' || *p
== ')' || *p
== '<' || *p
== '>'
200 || *p
== ',' || *p
== ';' || *p
== ':'
201 || *p
== '\\' || *p
== '[' || *p
== ']') {
208 np
->n_flags
|= NAME_MIME_INVALID
;
210 char ce
[sizeof(void*)];
211 /* 2012-09-25: dropped isprint(3) even if available
212 * since that fails for UTF-8 anyway */
213 if (err
>= 040 && err
<= 0177)
214 ce
[0] = (char)err
, ce
[1] = '\0';
216 snprintf(ce
, sizeof(ce
), "\\%03o", err
);
218 tr(143, "%s contains invalid character '%s'\n"),
226 * Check all addresses in np and delete invalid ones.
229 checkaddrs(struct name
*np
)
234 if (mime_name_invalid(n
, 1)) {
236 n
->n_blink
->n_flink
= n
->n_flink
;
238 n
->n_flink
->n_blink
= n
->n_blink
;
247 static char defcharset
[] = "utf-8";
250 * Get the character set dependant on the conversion.
253 getcharset(int isclean
)
257 if (isclean
& (MIME_CTRLCHAR
|MIME_HASNUL
))
259 else if (isclean
& MIME_HIGHBIT
) {
260 charset
= (wantcharset
&& wantcharset
!= (char *)-1) ?
261 wantcharset
: value("charset");
262 if (charset
== NULL
) {
263 charset
= defcharset
;
267 * This variable shall remain undocumented because
268 * only experts should change it.
270 charset
= value("charset7");
271 if (charset
== NULL
) {
279 * Get the setting of the terminal's character set.
286 if ((t
= value("ttycharset")) == NULL
)
287 if ((t
= value("charset")) == NULL
)
293 has_highbit(const char *s
)
299 while (*s
++ != '\0');
305 name_highbit(struct name
*np
)
308 if (has_highbit(np
->n_name
) || has_highbit(np
->n_fullname
))
316 need_hdrconv(struct header
*hp
, enum gfield w
)
320 if (name_highbit(hp
->h_from
))
322 } else if (has_highbit(myaddrs(hp
)))
324 if (hp
->h_organization
) {
325 if (has_highbit(hp
->h_organization
))
327 } else if (has_highbit(value("ORGANIZATION")))
330 if (name_highbit(hp
->h_replyto
))
332 } else if (has_highbit(value("replyto")))
335 if (name_highbit(hp
->h_sender
))
337 } else if (has_highbit(value("sender")))
340 if (w
& GTO
&& name_highbit(hp
->h_to
))
342 if (w
& GCC
&& name_highbit(hp
->h_cc
))
344 if (w
& GBCC
&& name_highbit(hp
->h_bcc
))
346 if (w
& GSUBJECT
&& has_highbit(hp
->h_subject
))
349 needs
: return getcharset(MIME_HIGHBIT
);
354 * Convert a string, upper-casing the characters.
357 uppercopy(char *dest
, const char *src
)
360 *dest
++ = upperconv(*src
& 0377);
379 * An iconv_open wrapper that tries to convert between character set
380 * naming conventions.
383 iconv_open_ft(const char *tocode
, const char *fromcode
)
389 * On Linux systems, this call may succeed.
391 if ((id
= iconv_open(tocode
, fromcode
)) != (iconv_t
)-1)
394 * Remove the "iso-" prefixes for Solaris.
396 if (ascncasecmp(tocode
, "iso-", 4) == 0)
398 else if (ascncasecmp(tocode
, "iso", 3) == 0)
400 if (ascncasecmp(fromcode
, "iso-", 4) == 0)
402 else if (ascncasecmp(fromcode
, "iso", 3) == 0)
404 if (*tocode
== '\0' || *fromcode
== '\0')
406 if ((id
= iconv_open(tocode
, fromcode
)) != (iconv_t
)-1)
409 * Solaris prefers upper-case charset names. Don't ask...
411 t
= salloc(strlen(tocode
) + 1);
412 uppercopy(t
, tocode
);
413 f
= salloc(strlen(fromcode
) + 1);
414 uppercopy(f
, fromcode
);
415 if ((id
= iconv_open(t
, f
)) != (iconv_t
)-1)
418 * Strip dashes for UnixWare.
422 if ((id
= iconv_open(t
, f
)) != (iconv_t
)-1)
425 * Add your vendor's sillynesses here.
428 * If the encoding names are equal at this point, they
429 * are just not understood by iconv(), and we cannot
430 * sensibly use it in any way. We do not perform this
431 * as an optimization above since iconv() can otherwise
432 * be used to check the validity of the input even with
433 * identical encoding names.
435 if (strcmp(t
, f
) == 0)
441 * Fault-tolerant iconv() function.
442 * (2012-09-24: export and use it exclusively to isolate prototype problems
443 * (*inb* is 'const char**' except in POSIX) in a single place.
444 * GNU libiconv even allows for configuration time const/non-const..
445 * In the end it's an ugly guess, but we can't do better since make(1) doesn't
446 * support compiler invocations which bail on error, so no -Werror.
448 /* Citrus project? */
449 #if defined _ICONV_H_ && defined __ICONV_F_HIDE_INVALID
450 # define __INBCAST (const char**)
457 iconv_ft(iconv_t cd
, char **inb
, size_t *inbleft
,
458 char **outb
, size_t *outbleft
, int tolerant
)
462 while ((sz
= iconv(cd
, __INBCAST inb
, inbleft
, outb
, outbleft
)) ==
465 && tolerant
&& (errno
== EILSEQ
|| errno
== EINVAL
)) {
485 * Print an error because of an invalid character sequence.
492 /*fprintf(stderr, "iconv: cannot convert %c\n", c);*/
494 #endif /* HAVE_ICONV */
497 is_this_enc(const char *line
, const char *encoding
)
503 while (*line
&& *encoding
)
504 if (c
= *line
++, lowerconv(c
) != *encoding
++)
506 if (quoted
&& *line
== '"')
508 if (*line
== '\0' || whitechar(*line
& 0377))
514 * Get the mime encoding from a Content-Transfer-Encoding header field.
519 if (is_this_enc(p
, "7bit"))
521 if (is_this_enc(p
, "8bit"))
523 if (is_this_enc(p
, "base64"))
525 if (is_this_enc(p
, "binary"))
527 if (is_this_enc(p
, "quoted-printable"))
533 * Get the mime content from a Content-Type header field, other parameters
537 mime_getcontent(char *s
)
539 if (strchr(s
, '/') == NULL
) /* for compatibility with non-MIME */
541 if (asccasecmp(s
, "text/plain") == 0)
542 return MIME_TEXT_PLAIN
;
543 if (asccasecmp(s
, "text/html") == 0)
544 return MIME_TEXT_HTML
;
545 if (ascncasecmp(s
, "text/", 5) == 0)
547 if (asccasecmp(s
, "message/rfc822") == 0)
549 if (ascncasecmp(s
, "message/", 8) == 0)
551 if (asccasecmp(s
, "multipart/alternative") == 0)
552 return MIME_ALTERNATIVE
;
553 if (asccasecmp(s
, "multipart/digest") == 0)
555 if (ascncasecmp(s
, "multipart/", 10) == 0)
557 if (asccasecmp(s
, "application/x-pkcs7-mime") == 0 ||
558 asccasecmp(s
, "application/pkcs7-mime") == 0)
564 * Get a mime style parameter from a header line.
567 mime_getparam(char *param
, char *h
)
574 if (!whitechar(*p
& 0377)) {
576 while (*p
&& (*p
!= ';' || c
== '\\')) {
577 c
= c
== '\\' ? '\0' : *p
;
584 while (whitechar(*p
& 0377))
586 if (ascncasecmp(p
, param
, sz
) == 0) {
588 while (whitechar(*p
& 0377))
594 while (*p
&& (*p
!= ';' || c
== '\\')) {
595 if (*p
== '"' && c
!= '\\') {
597 while (*p
&& (*p
!= '"' || c
== '\\')) {
598 c
= c
== '\\' ? '\0' : *p
;
603 c
= c
== '\\' ? '\0' : *p
;
610 while (whitechar(*p
& 0377))
616 if ((q
= strchr(p
, '"')) == NULL
)
620 while (*q
&& !whitechar(*q
& 0377) && *q
!= ';')
624 r
= salloc(q
- p
+ 1);
631 * Get the boundary out of a Content-Type: multipart/xyz header field.
634 mime_getboundary(char *h
)
639 if ((p
= mime_getparam("boundary", h
)) == NULL
)
644 memcpy(q
+ 2, p
, sz
);
645 *(q
+ sz
+ 2) = '\0';
650 * Get a line like "text/html html" and look if x matches the extension.
653 mime_tline(char *x
, char *l
)
658 if ((*l
& 0200) || alphachar(*l
& 0377) == 0)
661 while (blankchar(*l
& 0377) == 0 && *l
!= '\0')
666 while (blankchar(*l
& 0377) != 0 && *l
!= '\0')
672 while (whitechar(*l
& 0377) == 0 && *l
!= '\0')
676 if (strcmp(x
, n
) == 0) {
680 while (whitechar(*l
& 0377) != 0 && *l
!= '\0')
684 n
= salloc(strlen(type
) + 1);
692 * Check the given MIME type file for extension ext.
695 mime_type(char *ext
, char *filename
)
702 if ((f
= Fopen(filename
, "r")) == NULL
)
704 while (fgetline(&line
, &linesize
, NULL
, NULL
, f
, 0)) {
705 if ((type
= mime_tline(ext
, line
)) != NULL
)
715 * Return the Content-Type matching the extension of name.
718 mime_filecontent(char *name
)
722 if ((ext
= strrchr(name
, '.')) == NULL
|| *++ext
== '\0')
724 if ((content
= mime_type(ext
, expand(mimetypes_user
))) != NULL
)
726 if ((content
= mime_type(ext
, mimetypes_world
)) != NULL
)
732 * Check file contents.
734 static enum mimeclean
735 mime_isclean(FILE *f
)
738 unsigned curlen
= 1, maxlen
= 0, limit
= 950;
739 enum mimeclean isclean
= 0;
743 initial_pos
= ftell(f
);
748 if (c
== '\n' || c
== EOF
) {
750 * RFC 821 imposes a maximum line length of 1000
751 * characters including the terminating CRLF
752 * sequence. The configurable limit must not
753 * exceed that including a safety zone.
758 } else if (c
& 0200) {
759 isclean
|= MIME_HIGHBIT
;
760 } else if (c
== '\0') {
761 isclean
|= MIME_HASNUL
;
763 } else if ((c
< 040 && (c
!= '\t' && c
!= '\f')) || c
== 0177) {
764 isclean
|= MIME_CTRLCHAR
;
768 isclean
|= MIME_NOTERMNL
;
770 fseek(f
, initial_pos
, SEEK_SET
);
771 if ((cp
= value("maximum-unencoded-line-length")) != NULL
)
772 limit
= (unsigned)atoi(cp
);
776 isclean
|= MIME_LONGLINES
;
781 * Get the conversion that matches the encoding specified in the environment.
783 static enum conversion
784 gettextconversion(void)
789 if ((p
= value("encoding")) == NULL
)
791 if (strcmp(p
, "quoted-printable") == 0)
793 else if (strcmp(p
, "8bit") == 0)
796 fprintf(stderr
, tr(177,
797 "Warning: invalid encoding %s, using 8bit\n"), p
);
804 get_mime_convert(FILE *fp
, char **contenttype
, char **charset
,
805 enum mimeclean
*isclean
, int dosign
)
809 *isclean
= mime_isclean(fp
);
810 if (*isclean
& MIME_HASNUL
||
812 ascncasecmp(*contenttype
, "text/", 5))) {
813 convert
= CONV_TOB64
;
814 if (*contenttype
== NULL
||
815 ascncasecmp(*contenttype
, "text/", 5) == 0)
816 *contenttype
= "application/octet-stream";
818 } else if (*isclean
& (MIME_LONGLINES
|MIME_CTRLCHAR
|MIME_NOTERMNL
) ||
821 else if (*isclean
& MIME_HIGHBIT
)
822 convert
= gettextconversion();
825 if (*contenttype
== NULL
||
826 ascncasecmp(*contenttype
, "text/", 5) == 0) {
827 *charset
= getcharset(*isclean
);
828 if (wantcharset
== (char *)-1) {
829 *contenttype
= "application/octet-stream";
831 } if (*isclean
& MIME_CTRLCHAR
) {
832 convert
= CONV_TOB64
;
834 * RFC 2046 forbids control characters other than
835 * ^I or ^L in text/plain bodies. However, some
836 * obscure character sets actually contain these
837 * characters, so the content type can be set.
839 if ((*contenttype
= value("contenttype-cntrl")) == NULL
)
840 *contenttype
= "application/octet-stream";
841 } else if (*contenttype
== NULL
)
842 *contenttype
= "text/plain";
848 * Convert c to a hexadecimal character string and store it in hex.
851 ctohex(int c
, char *hex
)
857 hex
[1] = basetable
[d
];
859 hex
[0] = basetable
[(c
- d
) / 16];
861 hex
[0] = basetable
[0];
866 * Write to a file converting to quoted-printable.
867 * The mustquote function determines whether a character must be quoted.
870 mime_write_toqp(struct str
*in
, FILE *fo
, int (*mustquote
)(int))
872 char *p
, *upper
, *h
, hex
[3];
877 upper
= in
->s
+ in
->l
;
878 for (p
= in
->s
, l
= 0; p
< upper
; p
++) {
879 if (mustquote(*p
&0377) ||
880 (p
< upper
-1 && p
[1] == '\n' &&
881 blankchar(p
[0]&0377)) ||
882 (p
< upper
-4 && l
== 0 &&
883 p
[0] == 'F' && p
[1] == 'r' &&
884 p
[2] == 'o' && p
[3] == 'm') ||
885 (*p
== '.' && l
== 0 && p
< upper
-1 &&
889 fwrite("=\n", sizeof (char), 2, fo
);
894 h
= ctohex(*p
&0377, hex
);
895 fwrite(h
, sizeof *h
, 2, fo
);
902 fwrite("=\n", sizeof (char), 2, fo
);
913 * Write to a stringstruct converting to quoted-printable.
914 * The mustquote function determines whether a character must be quoted.
917 mime_str_toqp(struct str
*in
, struct str
*out
, int (*mustquote
)(int), int inhdr
)
921 out
->s
= smalloc(in
->l
* 3 + 1);
924 upper
= in
->s
+ in
->l
;
925 for (p
= in
->s
; p
< upper
; p
++) {
926 if (mustquote(*p
&0377) || (p
+1 < upper
&& *(p
+ 1) == '\n' &&
927 blankchar(*p
& 0377))) {
928 if (inhdr
&& *p
== ' ') {
944 * Write to a stringstruct converting from quoted-printable.
947 mime_fromqp(struct str
*in
, struct str
*out
, int ishdr
)
953 out
->s
= smalloc(out
->l
+ 1);
954 upper
= in
->s
+ in
->l
;
955 for (p
= in
->s
, q
= out
->s
; p
< upper
; p
++) {
960 } while (blankchar(*p
& 0377) && p
< upper
);
972 *q
= (char)strtol(quote
, NULL
, 16);
975 } else if (ishdr
&& *p
== '_')
983 #define mime_fromhdr_inc(inc) { \
984 size_t diff = q - out->s; \
985 out->s = srealloc(out->s, (maxstor += inc) + 1); \
986 q = &(out->s)[diff]; \
989 * Convert header fields from RFC 1522 format
992 mime_fromhdr(struct str
*in
, struct str
*out
, enum tdflags flags
)
994 char *p
, *q
, *op
, *upper
, *cs
, *cbeg
, *tcs
, *lastwordend
= NULL
;
995 struct str cin
, cout
;
997 size_t maxstor
, lastoutl
= 0;
999 iconv_t fhicd
= (iconv_t
)-1;
1002 tcs
= gettcharset();
1004 out
->s
= smalloc(maxstor
+ 1);
1006 upper
= in
->s
+ in
->l
;
1007 for (p
= in
->s
, q
= out
->s
; p
< upper
; p
++) {
1009 if (*p
== '=' && *(p
+ 1) == '?') {
1012 while (p
< upper
&& *p
!= '?')
1013 p
++; /* strip charset */
1016 cs
= salloc(++p
- cbeg
);
1017 memcpy(cs
, cbeg
, p
- cbeg
- 1);
1018 cs
[p
- cbeg
- 1] = '\0';
1020 if (fhicd
!= (iconv_t
)-1)
1022 if (strcmp(cs
, tcs
))
1023 fhicd
= iconv_open_ft(tcs
, cs
);
1025 fhicd
= (iconv_t
)-1;
1029 convert
= CONV_FROMB64
;
1032 convert
= CONV_FROMQP
;
1034 default: /* invalid, ignore */
1044 if (*p
++ == '?' && *p
== '=')
1051 mime_fromb64(&cin
, &cout
, 1);
1054 mime_fromqp(&cin
, &cout
, 1);
1062 if ((flags
& TD_ICONV
) && fhicd
!= (iconv_t
)-1) {
1063 char *iptr
, *mptr
, *nptr
, *uptr
;
1064 size_t inleft
, outleft
;
1066 again
: inleft
= cout
.l
;
1067 outleft
= maxstor
- out
->l
;
1069 uptr
= nptr
+ outleft
;
1071 if (iconv_ft(fhicd
, &iptr
, &inleft
,
1072 &nptr
, &outleft
, 0) == (size_t)-1 &&
1074 iconv_ft(fhicd
, NULL
, NULL
, NULL
, NULL
,
1076 mime_fromhdr_inc(inleft
);
1080 * For state-dependent encodings,
1081 * reset the state here, assuming
1082 * that states are restricted to
1083 * single encoded-word parts.
1085 while (iconv_ft(fhicd
, NULL
, NULL
,
1086 &nptr
, &outleft
, 0) == (size_t)-1 &&
1088 mime_fromhdr_inc(16);
1089 out
->l
+= uptr
- mptr
- outleft
;
1090 q
+= uptr
- mptr
- outleft
;
1093 while (cout
.l
> maxstor
- out
->l
)
1094 mime_fromhdr_inc(cout
.l
-
1095 (maxstor
- out
->l
));
1096 memcpy(q
, cout
.s
, cout
.l
);
1108 while (out
->l
>= maxstor
)
1109 mime_fromhdr_inc(16);
1112 if (!blankchar(*p
&0377))
1118 if (flags
& TD_ISPR
) {
1120 makeprint(out
, &new);
1124 if (flags
& TD_DELCTRL
)
1125 out
->l
= delctrl(out
->s
, out
->l
);
1127 if (fhicd
!= (iconv_t
)-1)
1134 * Convert header fields to RFC 1522 format and write to the file fo.
1137 mime_write_tohdr(struct str
*in
, FILE *fo
)
1139 char *upper
, *wbeg
, *wend
, *charset
, *lastwordend
= NULL
, *lastspc
, b
,
1141 struct str cin
, cout
;
1142 size_t sz
= 0, col
= 0, wr
, charsetlen
, charset7len
;
1143 int quoteany
, mustquote
, broken
,
1144 maxcol
= 65 /* there is the header field's name, too */;
1146 upper
= in
->s
+ in
->l
;
1147 charset
= getcharset(MIME_HIGHBIT
);
1148 if ((charset7
= value("charset7")) == NULL
)
1149 charset7
= us_ascii
;
1150 charsetlen
= strlen(charset
);
1151 charset7len
= strlen(charset7
);
1152 charsetlen
= smax(charsetlen
, charset7len
);
1154 for (wbeg
= in
->s
, quoteany
= 0; wbeg
< upper
; wbeg
++) {
1156 if (mustquote_hdr(wbeg
, wbeg
== in
->s
, wbeg
== &upper
[-1]))
1159 if (2u * quoteany
> in
->l
) {
1161 * Print the entire field in base64.
1163 for (wbeg
= in
->s
; wbeg
< upper
; wbeg
= wend
) {
1167 cin
.l
= wend
- wbeg
;
1168 if (cin
.l
* 4/3 + 7 + charsetlen
1170 fprintf(fo
, "=?%s?B?",
1171 b
&0200 ? charset
: charset7
);
1172 wr
= mime_write_tob64(&cin
, fo
, 1);
1173 fwrite("?=", sizeof (char), 2, fo
);
1174 wr
+= 7 + charsetlen
;
1175 sz
+= wr
, col
+= wr
;
1177 fwrite("\n ", sizeof (char),
1197 * Print the field word-wise in quoted-printable.
1200 for (wbeg
= in
->s
; wbeg
< upper
; wbeg
= wend
) {
1202 while (wbeg
< upper
&& whitechar(*wbeg
& 0377)) {
1203 lastspc
= lastspc
? lastspc
: wbeg
;
1208 if (wbeg
== upper
) {
1210 while (lastspc
< wbeg
) {
1211 putc(*lastspc
&0377, fo
);
1220 wend
< upper
&& !whitechar(*wend
& 0377);
1223 if (mustquote_hdr(wend
, wend
== wbeg
,
1224 wbeg
== &upper
[-1]))
1227 if (mustquote
|| broken
||
1228 ((wend
- wbeg
) >= 74 && quoteany
)) {
1230 cin
.s
= lastwordend
? lastwordend
:
1232 cin
.l
= wend
- cin
.s
;
1233 mime_str_toqp(&cin
, &cout
,
1234 mustquote_inhdrq
, 1);
1235 if ((wr
= cout
.l
+ charsetlen
+ 7)
1238 while (lastspc
< wbeg
) {
1245 fprintf(fo
, "=?%s?Q?", b
&0200 ?
1246 charset
: charset7
);
1247 fwrite(cout
.s
, sizeof *cout
.s
,
1249 fwrite("?=", 1, 2, fo
);
1250 sz
+= wr
, col
+= wr
;
1260 if (lastspc
== NULL
) {
1276 (size_t)(wend
- wbeg
) > maxcol
- col
) {
1281 if (lastspc
== NULL
) {
1286 maxcol
-= wbeg
- lastspc
;
1289 while (lastspc
< wbeg
) {
1290 putc(*lastspc
&0377, fo
);
1293 wr
= fwrite(wbeg
, sizeof *wbeg
,
1295 sz
+= wr
, col
+= wr
;
1304 * Write len characters of the passed string to the passed file,
1305 * doing charset and header conversion.
1308 convhdra(char *str
, size_t len
, FILE *fp
)
1319 cbuf
= ac_alloc(cbufsz
= 1);
1321 if (iconvd
== (iconv_t
)-1) {
1331 if (iconv_ft(iconvd
, &ip
, &isz
, &op
, &osz
, 0) == (size_t)-1) {
1332 if (errno
!= E2BIG
) {
1336 cbuf
= ac_alloc(cbufsz
+= isz
);
1340 cin
.l
= cbufsz
- osz
;
1342 #endif /* HAVE_ICONV */
1343 sz
= mime_write_tohdr(&cin
, fp
);
1350 * Write an address to a header field.
1353 mime_write_tohdr_a(struct str
*in
, FILE *f
)
1358 in
->s
[in
->l
] = '\0';
1360 if ((cp
= routeaddr(in
->s
)) != NULL
&& cp
> lastcp
) {
1361 sz
+= convhdra(lastcp
, cp
- lastcp
, f
);
1365 for ( ; *cp
; cp
++) {
1368 sz
+= fwrite(lastcp
, 1, cp
- lastcp
+ 1, f
);
1370 cp
= skip_comment(cp
);
1372 sz
+= convhdra(lastcp
, cp
- lastcp
, f
);
1379 if (*cp
== '\\' && cp
[1])
1386 sz
+= fwrite(lastcp
, 1, cp
- lastcp
, f
);
1391 addstr(char **buf
, size_t *sz
, size_t *pos
, char *str
, size_t len
)
1393 *buf
= srealloc(*buf
, *sz
+= len
);
1394 memcpy(&(*buf
)[*pos
], str
, len
);
1399 addconv(char **buf
, size_t *sz
, size_t *pos
, char *str
, size_t len
)
1405 mime_fromhdr(&in
, &out
, TD_ISPR
|TD_ICONV
);
1406 addstr(buf
, sz
, pos
, out
.s
, out
.l
);
1411 * Interpret MIME strings in parts of an address field.
1414 mime_fromaddr(char *name
)
1418 size_t ressz
= 1, rescur
= 0;
1420 if (name
== NULL
|| *name
== '\0')
1422 if ((cp
= routeaddr(name
)) != NULL
&& cp
> name
) {
1423 addconv(&res
, &ressz
, &rescur
, name
, cp
- name
);
1427 for ( ; *cp
; cp
++) {
1430 addstr(&res
, &ressz
, &rescur
, lastcp
, cp
- lastcp
+ 1);
1432 cp
= skip_comment(cp
);
1434 addconv(&res
, &ressz
, &rescur
, lastcp
,
1442 if (*cp
== '\\' && cp
[1])
1449 addstr(&res
, &ressz
, &rescur
, lastcp
, cp
- lastcp
);
1457 * fwrite whilst adding prefix, if present.
1460 prefixwrite(void *ptr
, size_t size
, size_t nmemb
, FILE *f
,
1461 char *prefix
, size_t prefixlen
)
1464 static char lastc
= '\n';
1465 size_t lpref
, i
, qfold
= 0, lnlen
= 0, rsz
= size
* nmemb
, wsz
= 0;
1470 if (prefix
== NULL
) {
1472 lastc
= ((char *)ptr
)[rsz
- 1];
1473 return fwrite(ptr
, 1, rsz
, f
);
1476 if ((p
= value("quote-fold")) != NULL
) {
1477 qfold
= (size_t)strtol(p
, NULL
, 10);
1478 if (qfold
< prefixlen
+ 4)
1479 qfold
= prefixlen
+ 4;
1480 --qfold
; /* The newline escape */
1483 if (f
!= lastf
|| lastc
== '\n') {
1484 wsz
+= fwrite(prefix
, sizeof *prefix
, prefixlen
, f
);
1501 wsz
+= fwrite(prefix
, sizeof *prefix
, prefixlen
, f
);
1506 * After writing a real newline followed by our prefix,
1507 * compress the quoted prefixes
1509 for (lpref
= 0; p
!= maxp
;) {
1510 /* (c: keep cc happy) */
1511 for (c
= i
= 0; p
+ i
< maxp
;) {
1513 if (blankspacechar(c
)) /* XXX U+A0+ */
1524 jquoteok
: lnlen
+= lpref
;
1527 * Search forward until either *quote-fold* or NL.
1528 * In the former case try to break at whitespace,
1529 * but only if that lies in the 2nd half of the data
1531 for (c
= rsz
= i
= 0; p
+ i
< maxp
;) {
1537 if (lnlen
+ i
>= qfold
) {
1539 if (rsz
> qfold
>> 1)
1546 wsz
+= fwrite(p
, sizeof *p
, i
, f
);
1558 wsz
+= fwrite(prefix
, sizeof *prefix
, prefixlen
, f
);
1564 for (; i
> 0; ++wsz
, ++lnlen
, --i
)
1579 * fwrite while checking for displayability.
1582 fwrite_td(void *ptr
, size_t size
, size_t nmemb
, FILE *f
, enum tdflags flags
,
1583 char *prefix
, size_t prefixlen
)
1589 size_t inleft
, outleft
;
1591 char *mptr
, *xmptr
, *mlptr
= NULL
;
1594 csize
= size
* nmemb
;
1596 mptr
= xmptr
= ac_alloc(mptrsz
+ 1);
1598 if ((flags
& TD_ICONV
) && iconvd
!= (iconv_t
)-1) {
1599 again
: inleft
= csize
;
1603 if (iconv_ft(iconvd
, &iptr
, &inleft
, &nptr
, &outleft
, 0) ==
1606 iconv_ft(iconvd
, NULL
, NULL
, NULL
, NULL
, 0);
1609 mptr
= ac_alloc(mptrsz
+ 1);
1612 nmemb
= mptrsz
- outleft
;
1613 size
= sizeof (char);
1615 csize
= size
* nmemb
;
1619 memcpy(mptr
, ptr
, csize
);
1621 upper
= mptr
+ csize
;
1623 if (flags
& TD_ISPR
) {
1627 makeprint(&in
, &out
);
1628 mptr
= mlptr
= out
.s
;
1631 if (flags
& TD_DELCTRL
)
1632 csize
= delctrl(mptr
, csize
);
1633 sz
= prefixwrite(mptr
, sizeof *mptr
, csize
, f
, prefix
, prefixlen
);
1640 * fwrite performing the given MIME conversion.
1643 mime_write(void *ptr
, size_t size
, FILE *f
,
1644 enum conversion convert
, enum tdflags dflags
,
1645 char *prefix
, size_t prefixlen
,
1646 char **restp
, size_t *restsizep
)
1652 char mptr
[LINESIZE
* 6];
1654 size_t inleft
, outleft
;
1661 if (csize
< sizeof mptr
&& (dflags
& TD_ICONV
)
1662 && iconvd
!= (iconv_t
)-1
1663 && (convert
== CONV_TOQP
|| convert
== CONV_8BIT
||
1664 convert
== CONV_TOB64
||
1665 convert
== CONV_TOHDR
)) {
1667 outleft
= sizeof mptr
;
1670 if (iconv_ft(iconvd
, &iptr
, &inleft
,
1671 &nptr
, &outleft
, 0) != (size_t)-1) {
1672 in
.l
= sizeof mptr
- outleft
;
1675 if (errno
== EILSEQ
|| errno
== EINVAL
)
1688 mime_fromqp(&in
, &out
, 0);
1689 sz
= fwrite_td(out
.s
, sizeof *out
.s
, out
.l
, f
, dflags
,
1694 sz
= mime_write_toqp(&in
, f
, mustquote_body
);
1697 sz
= prefixwrite(in
.s
, sizeof *in
.s
, in
.l
, f
,
1700 case CONV_FROMB64_T
:
1704 mime_fromb64_b(&in
, &out
, is_text
, f
);
1705 if (is_text
&& out
.s
[out
.l
-1] != '\n' && restp
&& restsizep
) {
1710 sz
= fwrite_td(out
.s
, sizeof *out
.s
, out
.l
, f
, dflags
,
1716 sz
= mime_write_tob64(&in
, f
, 0);
1719 mime_fromhdr(&in
, &out
, TD_ISPR
|TD_ICONV
);
1720 sz
= fwrite_td(out
.s
, sizeof *out
.s
, out
.l
, f
,
1721 dflags
&TD_DELCTRL
, prefix
, prefixlen
);
1725 sz
= mime_write_tohdr(&in
, f
);
1728 sz
= mime_write_tohdr_a(&in
, f
);
1731 sz
= fwrite_td(in
.s
, sizeof *in
.s
, in
.l
, f
, dflags
,