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))
145 static char defcharset
[] = "utf-8";
148 * Get the character set dependant on the conversion.
151 getcharset(int isclean
)
155 if (isclean
& (MIME_CTRLCHAR
|MIME_HASNUL
))
157 else if (isclean
& MIME_HIGHBIT
) {
158 charset
= (wantcharset
&& wantcharset
!= (char *)-1) ?
159 wantcharset
: value("charset");
160 if (charset
== NULL
) {
161 charset
= defcharset
;
165 * This variable shall remain undocumented because
166 * only experts should change it.
168 charset
= value("charset7");
169 if (charset
== NULL
) {
177 * Get the setting of the terminal's character set.
184 if ((t
= value("ttycharset")) == NULL
)
185 if ((t
= value("charset")) == NULL
)
191 has_highbit(const char *s
)
197 while (*s
++ != '\0');
203 name_highbit(struct name
*np
)
206 if (has_highbit(np
->n_name
) || has_highbit(np
->n_fullname
))
214 need_hdrconv(struct header
*hp
, enum gfield w
)
218 if (name_highbit(hp
->h_from
))
220 } else if (has_highbit(myaddrs(hp
)))
222 if (hp
->h_organization
) {
223 if (has_highbit(hp
->h_organization
))
225 } else if (has_highbit(value("ORGANIZATION")))
228 if (name_highbit(hp
->h_replyto
))
230 } else if (has_highbit(value("replyto")))
233 if (name_highbit(hp
->h_sender
))
235 } else if (has_highbit(value("sender")))
238 if (w
& GTO
&& name_highbit(hp
->h_to
))
240 if (w
& GCC
&& name_highbit(hp
->h_cc
))
242 if (w
& GBCC
&& name_highbit(hp
->h_bcc
))
244 if (w
& GSUBJECT
&& has_highbit(hp
->h_subject
))
247 needs
: return getcharset(MIME_HIGHBIT
);
252 * Convert a string, upper-casing the characters.
255 uppercopy(char *dest
, const char *src
)
258 *dest
++ = upperconv(*src
& 0377);
277 * An iconv_open wrapper that tries to convert between character set
278 * naming conventions.
281 iconv_open_ft(const char *tocode
, const char *fromcode
)
287 * On Linux systems, this call may succeed.
289 if ((id
= iconv_open(tocode
, fromcode
)) != (iconv_t
)-1)
292 * Remove the "iso-" prefixes for Solaris.
294 if (ascncasecmp(tocode
, "iso-", 4) == 0)
296 else if (ascncasecmp(tocode
, "iso", 3) == 0)
298 if (ascncasecmp(fromcode
, "iso-", 4) == 0)
300 else if (ascncasecmp(fromcode
, "iso", 3) == 0)
302 if (*tocode
== '\0' || *fromcode
== '\0')
304 if ((id
= iconv_open(tocode
, fromcode
)) != (iconv_t
)-1)
307 * Solaris prefers upper-case charset names. Don't ask...
309 t
= salloc(strlen(tocode
) + 1);
310 uppercopy(t
, tocode
);
311 f
= salloc(strlen(fromcode
) + 1);
312 uppercopy(f
, fromcode
);
313 if ((id
= iconv_open(t
, f
)) != (iconv_t
)-1)
316 * Strip dashes for UnixWare.
320 if ((id
= iconv_open(t
, f
)) != (iconv_t
)-1)
323 * Add your vendor's sillynesses here.
326 * If the encoding names are equal at this point, they
327 * are just not understood by iconv(), and we cannot
328 * sensibly use it in any way. We do not perform this
329 * as an optimization above since iconv() can otherwise
330 * be used to check the validity of the input even with
331 * identical encoding names.
333 if (strcmp(t
, f
) == 0)
339 * Fault-tolerant iconv() function.
340 * (2012-09-24: export and use it exclusively to isolate prototype problems
341 * (*inb* is 'const char**' except in POSIX) in a single place.
342 * GNU libiconv even allows for configuration time const/non-const..
343 * In the end it's an ugly guess, but we can't do better since make(1) doesn't
344 * support compiler invocations which bail on error, so no -Werror.
346 /* Citrus project? */
347 #if defined _ICONV_H_ && defined __ICONV_F_HIDE_INVALID
348 # define __INBCAST (const char**)
355 iconv_ft(iconv_t cd
, char **inb
, size_t *inbleft
,
356 char **outb
, size_t *outbleft
, int tolerant
)
360 while ((sz
= iconv(cd
, __INBCAST inb
, inbleft
, outb
, outbleft
)) ==
363 && tolerant
&& (errno
== EILSEQ
|| errno
== EINVAL
)) {
383 * Print an error because of an invalid character sequence.
390 /*fprintf(stderr, "iconv: cannot convert %c\n", c);*/
392 #endif /* HAVE_ICONV */
395 is_this_enc(const char *line
, const char *encoding
)
401 while (*line
&& *encoding
)
402 if (c
= *line
++, lowerconv(c
) != *encoding
++)
404 if (quoted
&& *line
== '"')
406 if (*line
== '\0' || whitechar(*line
& 0377))
412 * Get the mime encoding from a Content-Transfer-Encoding header field.
417 if (is_this_enc(p
, "7bit"))
419 if (is_this_enc(p
, "8bit"))
421 if (is_this_enc(p
, "base64"))
423 if (is_this_enc(p
, "binary"))
425 if (is_this_enc(p
, "quoted-printable"))
431 * Get the mime content from a Content-Type header field, other parameters
435 mime_getcontent(char *s
)
437 if (strchr(s
, '/') == NULL
) /* for compatibility with non-MIME */
439 if (asccasecmp(s
, "text/plain") == 0)
440 return MIME_TEXT_PLAIN
;
441 if (asccasecmp(s
, "text/html") == 0)
442 return MIME_TEXT_HTML
;
443 if (ascncasecmp(s
, "text/", 5) == 0)
445 if (asccasecmp(s
, "message/rfc822") == 0)
447 if (ascncasecmp(s
, "message/", 8) == 0)
449 if (asccasecmp(s
, "multipart/alternative") == 0)
450 return MIME_ALTERNATIVE
;
451 if (asccasecmp(s
, "multipart/digest") == 0)
453 if (ascncasecmp(s
, "multipart/", 10) == 0)
455 if (asccasecmp(s
, "application/x-pkcs7-mime") == 0 ||
456 asccasecmp(s
, "application/pkcs7-mime") == 0)
462 * Get a mime style parameter from a header line.
465 mime_getparam(char *param
, char *h
)
472 if (!whitechar(*p
& 0377)) {
474 while (*p
&& (*p
!= ';' || c
== '\\')) {
475 c
= c
== '\\' ? '\0' : *p
;
482 while (whitechar(*p
& 0377))
484 if (ascncasecmp(p
, param
, sz
) == 0) {
486 while (whitechar(*p
& 0377))
492 while (*p
&& (*p
!= ';' || c
== '\\')) {
493 if (*p
== '"' && c
!= '\\') {
495 while (*p
&& (*p
!= '"' || c
== '\\')) {
496 c
= c
== '\\' ? '\0' : *p
;
501 c
= c
== '\\' ? '\0' : *p
;
508 while (whitechar(*p
& 0377))
514 if ((q
= strchr(p
, '"')) == NULL
)
518 while (*q
&& !whitechar(*q
& 0377) && *q
!= ';')
522 r
= salloc(q
- p
+ 1);
529 * Get the boundary out of a Content-Type: multipart/xyz header field.
532 mime_getboundary(char *h
)
537 if ((p
= mime_getparam("boundary", h
)) == NULL
)
542 memcpy(q
+ 2, p
, sz
);
543 *(q
+ sz
+ 2) = '\0';
548 * Get a line like "text/html html" and look if x matches the extension.
551 mime_tline(char *x
, char *l
)
556 if ((*l
& 0200) || alphachar(*l
& 0377) == 0)
559 while (blankchar(*l
& 0377) == 0 && *l
!= '\0')
564 while (blankchar(*l
& 0377) != 0 && *l
!= '\0')
570 while (whitechar(*l
& 0377) == 0 && *l
!= '\0')
574 if (strcmp(x
, n
) == 0) {
578 while (whitechar(*l
& 0377) != 0 && *l
!= '\0')
582 n
= salloc(strlen(type
) + 1);
590 * Check the given MIME type file for extension ext.
593 mime_type(char *ext
, char *filename
)
600 if ((f
= Fopen(filename
, "r")) == NULL
)
602 while (fgetline(&line
, &linesize
, NULL
, NULL
, f
, 0)) {
603 if ((type
= mime_tline(ext
, line
)) != NULL
)
613 * Return the Content-Type matching the extension of name.
616 mime_filecontent(char *name
)
620 if ((ext
= strrchr(name
, '.')) == NULL
|| *++ext
== '\0')
622 if ((content
= mime_type(ext
, expand(mimetypes_user
))) != NULL
)
624 if ((content
= mime_type(ext
, mimetypes_world
)) != NULL
)
630 * Check file contents.
632 static enum mimeclean
633 mime_isclean(FILE *f
)
636 unsigned curlen
= 1, maxlen
= 0, limit
= 950;
637 enum mimeclean isclean
= 0;
641 initial_pos
= ftell(f
);
646 if (c
== '\n' || c
== EOF
) {
648 * RFC 821 imposes a maximum line length of 1000
649 * characters including the terminating CRLF
650 * sequence. The configurable limit must not
651 * exceed that including a safety zone.
656 } else if (c
& 0200) {
657 isclean
|= MIME_HIGHBIT
;
658 } else if (c
== '\0') {
659 isclean
|= MIME_HASNUL
;
661 } else if ((c
< 040 && (c
!= '\t' && c
!= '\f')) || c
== 0177) {
662 isclean
|= MIME_CTRLCHAR
;
666 isclean
|= MIME_NOTERMNL
;
668 fseek(f
, initial_pos
, SEEK_SET
);
669 if ((cp
= value("maximum-unencoded-line-length")) != NULL
)
670 limit
= (unsigned)atoi(cp
);
674 isclean
|= MIME_LONGLINES
;
679 * Get the conversion that matches the encoding specified in the environment.
681 static enum conversion
682 gettextconversion(void)
687 if ((p
= value("encoding")) == NULL
)
689 if (strcmp(p
, "quoted-printable") == 0)
691 else if (strcmp(p
, "8bit") == 0)
694 fprintf(stderr
, tr(177,
695 "Warning: invalid encoding %s, using 8bit\n"), p
);
701 /*TODO Dobson: be037047c, contenttype==NULL||"text"==NULL control flow! */
703 get_mime_convert(FILE *fp
, char **contenttype
, char **charset
,
704 enum mimeclean
*isclean
, int dosign
)
708 *isclean
= mime_isclean(fp
);
709 if (*isclean
& MIME_HASNUL
||
711 ascncasecmp(*contenttype
, "text/", 5))) {
712 convert
= CONV_TOB64
;
713 if (*contenttype
== NULL
||
714 ascncasecmp(*contenttype
, "text/", 5) == 0)
715 *contenttype
= "application/octet-stream";
717 } else if (*isclean
& (MIME_LONGLINES
|MIME_CTRLCHAR
|MIME_NOTERMNL
) ||
720 else if (*isclean
& MIME_HIGHBIT
)
721 convert
= gettextconversion();
724 if (*contenttype
== NULL
||
725 ascncasecmp(*contenttype
, "text/", 5) == 0) {
726 *charset
= getcharset(*isclean
);
727 if (wantcharset
== (char *)-1) {
728 *contenttype
= "application/octet-stream";
730 } if (*isclean
& MIME_CTRLCHAR
) {
731 convert
= CONV_TOB64
;
733 * RFC 2046 forbids control characters other than
734 * ^I or ^L in text/plain bodies. However, some
735 * obscure character sets actually contain these
736 * characters, so the content type can be set.
738 if ((*contenttype
= value("contenttype-cntrl")) == NULL
)
739 *contenttype
= "application/octet-stream";
740 } else if (*contenttype
== NULL
)
741 *contenttype
= "text/plain";
747 * Convert c to a hexadecimal character string and store it in hex.
750 ctohex(int c
, char *hex
)
756 hex
[1] = basetable
[d
];
758 hex
[0] = basetable
[(c
- d
) / 16];
760 hex
[0] = basetable
[0];
765 * Write to a file converting to quoted-printable.
766 * The mustquote function determines whether a character must be quoted.
769 mime_write_toqp(struct str
*in
, FILE *fo
, int (*mustquote
)(int))
771 char *p
, *upper
, *h
, hex
[3];
776 upper
= in
->s
+ in
->l
;
777 for (p
= in
->s
, l
= 0; p
< upper
; p
++) {
779 (p
< upper
- 1 && p
[1] == '\n' &&
781 (p
< upper
- 4 && l
== 0 &&
782 *p
== 'F' && p
[1] == 'r' &&
783 p
[2] == 'o' && p
[3] == 'm') ||
784 (*p
== '.' && l
== 0 && p
< upper
- 1 &&
788 fwrite("=\n", sizeof (char), 2, fo
);
794 fwrite(h
, sizeof *h
, 2, fo
);
801 fwrite("=\n", sizeof (char), 2, fo
);
812 * Write to a stringstruct converting to quoted-printable.
813 * The mustquote function determines whether a character must be quoted.
816 mime_str_toqp(struct str
*in
, struct str
*out
, int (*mustquote
)(int), int inhdr
)
820 out
->s
= smalloc(in
->l
* 3 + 1);
823 upper
= in
->s
+ in
->l
;
824 for (p
= in
->s
; p
< upper
; p
++) {
825 if (mustquote(*p
&0377) || (p
+1 < upper
&& *(p
+ 1) == '\n' &&
826 blankchar(*p
& 0377))) {
827 if (inhdr
&& *p
== ' ') {
843 * Write to a stringstruct converting from quoted-printable.
846 mime_fromqp(struct str
*in
, struct str
*out
, int ishdr
)
852 out
->s
= smalloc(out
->l
+ 1);
853 upper
= in
->s
+ in
->l
;
854 for (p
= in
->s
, q
= out
->s
; p
< upper
; p
++) {
859 } while (blankchar(*p
& 0377) && p
< upper
);
871 *q
= (char)strtol(quote
, NULL
, 16);
874 } else if (ishdr
&& *p
== '_')
882 #define mime_fromhdr_inc(inc) { \
883 size_t diff = q - out->s; \
884 out->s = srealloc(out->s, (maxstor += inc) + 1); \
885 q = &(out->s)[diff]; \
888 * Convert header fields from RFC 1522 format
891 mime_fromhdr(struct str
*in
, struct str
*out
, enum tdflags flags
)
893 char *p
, *q
, *op
, *upper
, *cs
, *cbeg
, *tcs
, *lastwordend
= NULL
;
894 struct str cin
, cout
;
896 size_t maxstor
, lastoutl
= 0;
898 iconv_t fhicd
= (iconv_t
)-1;
903 out
->s
= smalloc(maxstor
+ 1);
905 upper
= in
->s
+ in
->l
;
906 for (p
= in
->s
, q
= out
->s
; p
< upper
; p
++) {
908 if (*p
== '=' && *(p
+ 1) == '?') {
911 while (p
< upper
&& *p
!= '?')
912 p
++; /* strip charset */
915 cs
= salloc(++p
- cbeg
);
916 memcpy(cs
, cbeg
, p
- cbeg
- 1);
917 cs
[p
- cbeg
- 1] = '\0';
919 if (fhicd
!= (iconv_t
)-1)
922 fhicd
= iconv_open_ft(tcs
, cs
);
928 convert
= CONV_FROMB64
;
931 convert
= CONV_FROMQP
;
933 default: /* invalid, ignore */
943 if (*p
++ == '?' && *p
== '=')
950 mime_fromb64(&cin
, &cout
, 1);
953 mime_fromqp(&cin
, &cout
, 1);
961 if ((flags
& TD_ICONV
) && fhicd
!= (iconv_t
)-1) {
962 char *iptr
, *mptr
, *nptr
, *uptr
;
963 size_t inleft
, outleft
;
965 again
: inleft
= cout
.l
;
966 outleft
= maxstor
- out
->l
;
968 uptr
= nptr
+ outleft
;
970 if (iconv_ft(fhicd
, &iptr
, &inleft
,
971 &nptr
, &outleft
, 0) == (size_t)-1 &&
973 iconv_ft(fhicd
, NULL
, NULL
, NULL
, NULL
,
975 mime_fromhdr_inc(inleft
);
979 * For state-dependent encodings,
980 * reset the state here, assuming
981 * that states are restricted to
982 * single encoded-word parts.
984 while (iconv_ft(fhicd
, NULL
, NULL
,
985 &nptr
, &outleft
, 0) == (size_t)-1 &&
987 mime_fromhdr_inc(16);
988 out
->l
+= uptr
- mptr
- outleft
;
989 q
+= uptr
- mptr
- outleft
;
992 while (cout
.l
> maxstor
- out
->l
)
993 mime_fromhdr_inc(cout
.l
-
995 memcpy(q
, cout
.s
, cout
.l
);
1007 while (out
->l
>= maxstor
)
1008 mime_fromhdr_inc(16);
1011 if (!blankchar(*p
&0377))
1017 if (flags
& TD_ISPR
) {
1019 makeprint(out
, &new);
1023 if (flags
& TD_DELCTRL
)
1024 out
->l
= delctrl(out
->s
, out
->l
);
1026 if (fhicd
!= (iconv_t
)-1)
1033 * Convert header fields to RFC 1522 format and write to the file fo.
1036 mime_write_tohdr(struct str
*in
, FILE *fo
)
1038 char *upper
, *wbeg
, *wend
, *charset
, *lastwordend
= NULL
, *lastspc
, b
,
1040 struct str cin
, cout
;
1041 size_t sz
= 0, col
= 0, wr
, charsetlen
, charset7len
;
1042 int quoteany
, mustquote
, broken
,
1043 maxcol
= 65 /* there is the header field's name, too */;
1045 upper
= in
->s
+ in
->l
;
1046 charset
= getcharset(MIME_HIGHBIT
);
1047 if ((charset7
= value("charset7")) == NULL
)
1048 charset7
= us_ascii
;
1049 charsetlen
= strlen(charset
);
1050 charset7len
= strlen(charset7
);
1051 charsetlen
= smax(charsetlen
, charset7len
);
1053 for (wbeg
= in
->s
, quoteany
= 0; wbeg
< upper
; wbeg
++) {
1055 if (mustquote_hdr(wbeg
, wbeg
== in
->s
, wbeg
== &upper
[-1]))
1058 if (2u * quoteany
> in
->l
) {
1060 * Print the entire field in base64.
1062 for (wbeg
= in
->s
; wbeg
< upper
; wbeg
= wend
) {
1066 cin
.l
= wend
- wbeg
;
1067 if (cin
.l
* 4/3 + 7 + charsetlen
1069 fprintf(fo
, "=?%s?B?",
1070 b
&0200 ? charset
: charset7
);
1071 wr
= mime_write_tob64(&cin
, fo
, 1);
1072 fwrite("?=", sizeof (char), 2, fo
);
1073 wr
+= 7 + charsetlen
;
1074 sz
+= wr
, col
+= wr
;
1076 fwrite("\n ", sizeof (char),
1096 * Print the field word-wise in quoted-printable.
1099 for (wbeg
= in
->s
; wbeg
< upper
; wbeg
= wend
) {
1101 while (wbeg
< upper
&& whitechar(*wbeg
& 0377)) {
1102 lastspc
= lastspc
? lastspc
: wbeg
;
1107 if (wbeg
== upper
) {
1109 while (lastspc
< wbeg
) {
1110 putc(*lastspc
&0377, fo
);
1119 wend
< upper
&& !whitechar(*wend
& 0377);
1122 if (mustquote_hdr(wend
, wend
== wbeg
,
1123 wbeg
== &upper
[-1]))
1126 if (mustquote
|| broken
||
1127 ((wend
- wbeg
) >= 74 && quoteany
)) {
1129 cin
.s
= lastwordend
? lastwordend
:
1131 cin
.l
= wend
- cin
.s
;
1132 mime_str_toqp(&cin
, &cout
,
1133 mustquote_inhdrq
, 1);
1134 if ((wr
= cout
.l
+ charsetlen
+ 7)
1137 while (lastspc
< wbeg
) {
1144 fprintf(fo
, "=?%s?Q?", b
&0200 ?
1145 charset
: charset7
);
1146 fwrite(cout
.s
, sizeof *cout
.s
,
1148 fwrite("?=", 1, 2, fo
);
1149 sz
+= wr
, col
+= wr
;
1159 if (lastspc
== NULL
) {
1175 (size_t)(wend
- wbeg
) > maxcol
- col
) {
1180 if (lastspc
== NULL
) {
1185 maxcol
-= wbeg
- lastspc
;
1188 while (lastspc
< wbeg
) {
1189 putc(*lastspc
&0377, fo
);
1192 wr
= fwrite(wbeg
, sizeof *wbeg
,
1194 sz
+= wr
, col
+= wr
;
1203 * Write len characters of the passed string to the passed file,
1204 * doing charset and header conversion.
1207 convhdra(char *str
, size_t len
, FILE *fp
)
1218 cbuf
= ac_alloc(cbufsz
= 1);
1220 if (iconvd
== (iconv_t
)-1) {
1230 if (iconv_ft(iconvd
, &ip
, &isz
, &op
, &osz
, 0) == (size_t)-1) {
1231 if (errno
!= E2BIG
) {
1235 cbuf
= ac_alloc(cbufsz
+= isz
);
1239 cin
.l
= cbufsz
- osz
;
1241 #endif /* HAVE_ICONV */
1242 sz
= mime_write_tohdr(&cin
, fp
);
1249 * Write an address to a header field.
1252 mime_write_tohdr_a(struct str
*in
, FILE *f
)
1257 in
->s
[in
->l
] = '\0';
1259 if ((cp
= routeaddr(in
->s
)) != NULL
&& cp
> lastcp
) {
1260 sz
+= convhdra(lastcp
, cp
- lastcp
, f
);
1264 for ( ; *cp
; cp
++) {
1267 sz
+= fwrite(lastcp
, 1, cp
- lastcp
+ 1, f
);
1269 cp
= skip_comment(cp
);
1271 sz
+= convhdra(lastcp
, cp
- lastcp
, f
);
1278 if (*cp
== '\\' && cp
[1])
1285 sz
+= fwrite(lastcp
, 1, cp
- lastcp
, f
);
1290 addstr(char **buf
, size_t *sz
, size_t *pos
, char *str
, size_t len
)
1292 *buf
= srealloc(*buf
, *sz
+= len
);
1293 memcpy(&(*buf
)[*pos
], str
, len
);
1298 addconv(char **buf
, size_t *sz
, size_t *pos
, char *str
, size_t len
)
1304 mime_fromhdr(&in
, &out
, TD_ISPR
|TD_ICONV
);
1305 addstr(buf
, sz
, pos
, out
.s
, out
.l
);
1310 * Interpret MIME strings in parts of an address field.
1313 mime_fromaddr(char *name
)
1317 size_t ressz
= 1, rescur
= 0;
1319 if (name
== NULL
|| *name
== '\0')
1321 if ((cp
= routeaddr(name
)) != NULL
&& cp
> name
) {
1322 addconv(&res
, &ressz
, &rescur
, name
, cp
- name
);
1326 for ( ; *cp
; cp
++) {
1329 addstr(&res
, &ressz
, &rescur
, lastcp
, cp
- lastcp
+ 1);
1331 cp
= skip_comment(cp
);
1333 addconv(&res
, &ressz
, &rescur
, lastcp
,
1341 if (*cp
== '\\' && cp
[1])
1348 addstr(&res
, &ressz
, &rescur
, lastcp
, cp
- lastcp
);
1356 * fwrite whilst adding prefix, if present.
1359 prefixwrite(void *ptr
, size_t size
, size_t nmemb
, FILE *f
,
1360 char *prefix
, size_t prefixlen
)
1363 static char lastc
= '\n';
1364 size_t lpref
, i
, qfold
= 0, lnlen
= 0, rsz
= size
* nmemb
, wsz
= 0;
1371 return fwrite(ptr
, 1, rsz
, f
);
1373 if ((p
= value("quote-fold")) != NULL
) {
1374 qfold
= (size_t)strtol(p
, NULL
, 10);
1375 if (qfold
< prefixlen
+ 4)
1376 qfold
= prefixlen
+ 4;
1377 --qfold
; /* The newline escape */
1380 if (f
!= lastf
|| lastc
== '\n') {
1381 wsz
+= fwrite(prefix
, sizeof *prefix
, prefixlen
, f
);
1398 wsz
+= fwrite(prefix
, sizeof *prefix
, prefixlen
, f
);
1403 * After writing a real newline followed by our prefix,
1404 * compress the quoted prefixes
1406 for (lpref
= 0; p
!= maxp
;) {
1407 /* (c: keep cc happy) */
1408 for (c
= i
= 0; p
+ i
< maxp
;) {
1410 if (blankspacechar(c
))
1421 jquoteok
: lnlen
+= lpref
;
1424 * Search forward until either *quote-fold* or NL.
1425 * In the former case try to break at whitespace,
1426 * but only if that lies in the 2nd half of the data
1428 for (c
= rsz
= i
= 0; p
+ i
< maxp
;) {
1434 if (lnlen
+ i
>= qfold
) {
1436 if (rsz
> qfold
>> 1)
1443 wsz
+= fwrite(p
, sizeof *p
, i
, f
);
1455 wsz
+= fwrite(prefix
, sizeof *prefix
, prefixlen
, f
);
1461 for (; i
> 0; ++wsz
, ++lnlen
, --i
)
1476 * fwrite while checking for displayability.
1479 fwrite_td(void *ptr
, size_t size
, size_t nmemb
, FILE *f
, enum tdflags flags
,
1480 char *prefix
, size_t prefixlen
)
1486 size_t inleft
, outleft
;
1488 char *mptr
, *xmptr
, *mlptr
= NULL
;
1491 csize
= size
* nmemb
;
1493 mptr
= xmptr
= ac_alloc(mptrsz
+ 1);
1495 if ((flags
& TD_ICONV
) && iconvd
!= (iconv_t
)-1) {
1496 again
: inleft
= csize
;
1500 if (iconv_ft(iconvd
, &iptr
, &inleft
, &nptr
, &outleft
, 0) ==
1503 iconv_ft(iconvd
, NULL
, NULL
, NULL
, NULL
, 0);
1506 mptr
= ac_alloc(mptrsz
+ 1);
1509 nmemb
= mptrsz
- outleft
;
1510 size
= sizeof (char);
1512 csize
= size
* nmemb
;
1516 memcpy(mptr
, ptr
, csize
);
1518 upper
= mptr
+ csize
;
1520 if (flags
& TD_ISPR
) {
1524 makeprint(&in
, &out
);
1525 mptr
= mlptr
= out
.s
;
1528 if (flags
& TD_DELCTRL
)
1529 csize
= delctrl(mptr
, csize
);
1530 sz
= prefixwrite(mptr
, sizeof *mptr
, csize
, f
, prefix
, prefixlen
);
1537 * fwrite performing the given MIME conversion.
1540 mime_write(void *ptr
, size_t size
, FILE *f
,
1541 enum conversion convert
, enum tdflags dflags
,
1542 char *prefix
, size_t prefixlen
,
1543 char **restp
, size_t *restsizep
)
1549 char mptr
[LINESIZE
* 6];
1551 size_t inleft
, outleft
;
1558 if (csize
< sizeof mptr
&& (dflags
& TD_ICONV
)
1559 && iconvd
!= (iconv_t
)-1
1560 && (convert
== CONV_TOQP
|| convert
== CONV_8BIT
||
1561 convert
== CONV_TOB64
||
1562 convert
== CONV_TOHDR
)) {
1564 outleft
= sizeof mptr
;
1567 if (iconv_ft(iconvd
, &iptr
, &inleft
,
1568 &nptr
, &outleft
, 0) != (size_t)-1) {
1569 in
.l
= sizeof mptr
- outleft
;
1572 if (errno
== EILSEQ
|| errno
== EINVAL
)
1585 mime_fromqp(&in
, &out
, 0);
1586 sz
= fwrite_td(out
.s
, sizeof *out
.s
, out
.l
, f
, dflags
,
1591 sz
= mime_write_toqp(&in
, f
, mustquote_body
);
1594 sz
= prefixwrite(in
.s
, sizeof *in
.s
, in
.l
, f
,
1597 case CONV_FROMB64_T
:
1601 mime_fromb64_b(&in
, &out
, is_text
, f
);
1602 if (is_text
&& out
.s
[out
.l
-1] != '\n' && restp
&& restsizep
) {
1607 sz
= fwrite_td(out
.s
, sizeof *out
.s
, out
.l
, f
, dflags
,
1613 sz
= mime_write_tob64(&in
, f
, 0);
1616 mime_fromhdr(&in
, &out
, TD_ISPR
|TD_ICONV
);
1617 sz
= fwrite_td(out
.s
, sizeof *out
.s
, out
.l
, f
,
1618 dflags
&TD_DELCTRL
, prefix
, prefixlen
);
1622 sz
= mime_write_tohdr(&in
, f
);
1625 sz
= mime_write_tohdr_a(&in
, f
);
1628 sz
= fwrite_td(in
.s
, sizeof *in
.s
, in
.l
, f
, dflags
,