2 * Heirloom mailx - 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
42 static char copyright
[]
43 = "@(#) Copyright (c) 2000, 2002 Gunnar Ritter. All rights reserved.\n";
44 static char sccsid
[] = "@(#)mime.c 2.71 (gritter) 7/5/10";
54 #endif /* HAVE_WCTYPE_H */
57 * Mail -- a mail program
59 * MIME support functions.
63 * You won't guess what these are for.
65 static const char basetable
[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
66 static char *mimetypes_world
= "/etc/mime.types";
67 static char *mimetypes_user
= "~/.mime.types";
68 char *us_ascii
= "us-ascii";
70 static int mustquote_body(int c
);
71 static int mustquote_hdr(const char *cp
, int wordstart
, int wordend
);
72 static int mustquote_inhdrq(int c
);
73 static size_t delctrl(char *cp
, size_t sz
);
74 static char *getcharset(int isclean
);
75 static int has_highbit(register const char *s
);
77 static void uppercopy(char *dest
, const char *src
);
78 static void stripdash(char *p
);
79 static size_t iconv_ft(iconv_t cd
, char **inb
, size_t *inbleft
,
80 char **outb
, size_t *outbleft
);
81 static void invalid_seq(int c
);
82 #endif /* HAVE_ICONV */
83 static int is_this_enc(const char *line
, const char *encoding
);
84 static char *mime_tline(char *x
, char *l
);
85 static char *mime_type(char *ext
, char *filename
);
86 static enum mimeclean
mime_isclean(FILE *f
);
87 static enum conversion
gettextconversion(void);
88 static char *ctohex(int c
, char *hex
);
89 static size_t mime_write_toqp(struct str
*in
, FILE *fo
, int (*mustquote
)(int));
90 static void mime_str_toqp(struct str
*in
, struct str
*out
,
91 int (*mustquote
)(int), int inhdr
);
92 static void mime_fromqp(struct str
*in
, struct str
*out
, int ishdr
);
93 static size_t mime_write_tohdr(struct str
*in
, FILE *fo
);
94 static size_t convhdra(char *str
, size_t len
, FILE *fp
);
95 static size_t mime_write_tohdr_a(struct str
*in
, FILE *f
);
96 static void addstr(char **buf
, size_t *sz
, size_t *pos
, char *str
, size_t len
);
97 static void addconv(char **buf
, size_t *sz
, size_t *pos
, char *str
, size_t len
);
98 static size_t fwrite_td(void *ptr
, size_t size
, size_t nmemb
, FILE *f
,
99 enum tdflags flags
, char *prefix
, size_t prefixlen
);
102 * Check if c must be quoted inside a message's body.
105 mustquote_body(int c
)
107 if (c
!= '\n' && (c
< 040 || c
== '=' || c
>= 0177))
113 * Check if c must be quoted inside a message's header.
116 mustquote_hdr(const char *cp
, int wordstart
, int wordend
)
120 if (c
!= '\n' && (c
< 040 || c
>= 0177))
122 if (wordstart
&& cp
[0] == '=' && cp
[1] == '?')
124 if (cp
[0] == '?' && cp
[1] == '=' &&
125 (wordend
|| cp
[2] == '\0' || whitechar(cp
[2]&0377)))
131 * Check if c must be quoted inside a quoting in a message's header.
134 mustquote_inhdrq(int c
)
137 && (c
<= 040 || c
== '=' || c
== '?' || c
== '_' || c
>= 0177))
143 delctrl(char *cp
, size_t sz
)
148 if (!cntrlchar(cp
[x
]&0377))
156 * Check if a name's address part contains invalid characters.
159 mime_name_invalid(char *name
, int putmsg
)
162 int in_quote
= 0, in_domain
= 0, err
= 0, hadat
= 0;
164 if (is_fileaddr(name
))
168 if (addr
== NULL
|| *addr
== '\0')
170 for (p
= addr
; *p
!= '\0'; p
++) {
172 in_quote
= !in_quote
;
173 } else if (*p
< 040 || (*p
& 0377) >= 0177) {
176 } else if (in_domain
== 2) {
177 if ((*p
== ']' && p
[1] != '\0') || *p
== '\0'
178 || *p
== '\\' || whitechar(*p
& 0377)) {
182 } else if (in_quote
&& in_domain
== 0) {
184 } else if (*p
== '\\' && p
[1] != '\0') {
186 } else if (*p
== '@') {
189 fprintf(stderr
, catgets(catd
, CATSET
,
191 "%s contains invalid @@ sequence\n"),
203 } else if (*p
== '(' || *p
== ')' || *p
== '<' || *p
== '>'
204 || *p
== ',' || *p
== ';' || *p
== ':'
205 || *p
== '\\' || *p
== '[' || *p
== ']') {
212 fprintf(stderr
, catgets(catd
, CATSET
, 143,
213 "%s contains invalid character '"), addr
);
214 #ifdef HAVE_SETLOCALE
216 #else /* !HAVE_SETLOCALE */
217 if (err
>= 040 && err
<= 0177)
218 #endif /* !HAVE_SETLOCALE */
221 fprintf(stderr
, "\\%03o", err
);
222 fprintf(stderr
, catgets(catd
, CATSET
, 144, "'\n"));
228 * Check all addresses in np and delete invalid ones.
231 checkaddrs(struct name
*np
)
236 if (mime_name_invalid(n
->n_name
, 1)) {
238 n
->n_blink
->n_flink
= n
->n_flink
;
240 n
->n_flink
->n_blink
= n
->n_blink
;
249 static char defcharset
[] = "utf-8";
252 * Get the character set dependant on the conversion.
255 getcharset(int isclean
)
259 if (isclean
& (MIME_CTRLCHAR
|MIME_HASNUL
))
261 else if (isclean
& MIME_HIGHBIT
) {
262 charset
= (wantcharset
&& wantcharset
!= (char *)-1) ?
263 wantcharset
: value("charset");
264 if (charset
== NULL
) {
265 charset
= defcharset
;
269 * This variable shall remain undocumented because
270 * only experts should change it.
272 charset
= value("charset7");
273 if (charset
== NULL
) {
281 * Get the setting of the terminal's character set.
288 if ((t
= value("ttycharset")) == NULL
)
289 if ((t
= value("charset")) == NULL
)
295 has_highbit(const char *s
)
301 while (*s
++ != '\0');
307 name_highbit(struct name
*np
)
310 if (has_highbit(np
->n_name
) || has_highbit(np
->n_fullname
))
318 need_hdrconv(struct header
*hp
, enum gfield w
)
322 if (name_highbit(hp
->h_from
))
324 } else if (has_highbit(myaddrs(hp
)))
326 if (hp
->h_organization
) {
327 if (has_highbit(hp
->h_organization
))
329 } else if (has_highbit(value("ORGANIZATION")))
332 if (name_highbit(hp
->h_replyto
))
334 } else if (has_highbit(value("replyto")))
337 if (name_highbit(hp
->h_sender
))
339 } else if (has_highbit(value("sender")))
342 if (w
& GTO
&& name_highbit(hp
->h_to
))
344 if (w
& GCC
&& name_highbit(hp
->h_cc
))
346 if (w
& GBCC
&& name_highbit(hp
->h_bcc
))
348 if (w
& GSUBJECT
&& has_highbit(hp
->h_subject
))
351 needs
: return getcharset(MIME_HIGHBIT
);
356 * Convert a string, upper-casing the characters.
359 uppercopy(char *dest
, const char *src
)
362 *dest
++ = upperconv(*src
& 0377);
381 * An iconv_open wrapper that tries to convert between character set
382 * naming conventions.
385 iconv_open_ft(const char *tocode
, const char *fromcode
)
391 * On Linux systems, this call may succeed.
393 if ((id
= iconv_open(tocode
, fromcode
)) != (iconv_t
)-1)
396 * Remove the "iso-" prefixes for Solaris.
398 if (ascncasecmp(tocode
, "iso-", 4) == 0)
400 else if (ascncasecmp(tocode
, "iso", 3) == 0)
402 if (ascncasecmp(fromcode
, "iso-", 4) == 0)
404 else if (ascncasecmp(fromcode
, "iso", 3) == 0)
406 if (*tocode
== '\0' || *fromcode
== '\0')
408 if ((id
= iconv_open(tocode
, fromcode
)) != (iconv_t
)-1)
411 * Solaris prefers upper-case charset names. Don't ask...
413 t
= salloc(strlen(tocode
) + 1);
414 uppercopy(t
, tocode
);
415 f
= salloc(strlen(fromcode
) + 1);
416 uppercopy(f
, fromcode
);
417 if ((id
= iconv_open(t
, f
)) != (iconv_t
)-1)
420 * Strip dashes for UnixWare.
424 if ((id
= iconv_open(t
, f
)) != (iconv_t
)-1)
427 * Add your vendor's sillynesses here.
430 * If the encoding names are equal at this point, they
431 * are just not understood by iconv(), and we cannot
432 * sensibly use it in any way. We do not perform this
433 * as an optimization above since iconv() can otherwise
434 * be used to check the validity of the input even with
435 * identical encoding names.
437 if (strcmp(t
, f
) == 0)
443 * Fault-tolerant iconv() function.
446 iconv_ft(iconv_t cd
, char **inb
, size_t *inbleft
, char **outb
, size_t *outbleft
)
450 while ((sz
= iconv(cd
, inb
, inbleft
, outb
, outbleft
)) == (size_t)-1
451 && (errno
== EILSEQ
|| errno
== EINVAL
)) {
471 * Print an error because of an invalid character sequence.
478 /*fprintf(stderr, "iconv: cannot convert %c\n", c);*/
480 #endif /* HAVE_ICONV */
483 is_this_enc(const char *line
, const char *encoding
)
489 while (*line
&& *encoding
)
490 if (c
= *line
++, lowerconv(c
) != *encoding
++)
492 if (quoted
&& *line
== '"')
494 if (*line
== '\0' || whitechar(*line
& 0377))
500 * Get the mime encoding from a Content-Transfer-Encoding header field.
505 if (is_this_enc(p
, "7bit"))
507 if (is_this_enc(p
, "8bit"))
509 if (is_this_enc(p
, "base64"))
511 if (is_this_enc(p
, "binary"))
513 if (is_this_enc(p
, "quoted-printable"))
519 * Get the mime content from a Content-Type header field, other parameters
523 mime_getcontent(char *s
)
525 if (strchr(s
, '/') == NULL
) /* for compatibility with non-MIME */
527 if (asccasecmp(s
, "text/plain") == 0)
528 return MIME_TEXT_PLAIN
;
529 if (asccasecmp(s
, "text/html") == 0)
530 return MIME_TEXT_HTML
;
531 if (ascncasecmp(s
, "text/", 5) == 0)
533 if (asccasecmp(s
, "message/rfc822") == 0)
535 if (ascncasecmp(s
, "message/", 8) == 0)
537 if (asccasecmp(s
, "multipart/alternative") == 0)
538 return MIME_ALTERNATIVE
;
539 if (asccasecmp(s
, "multipart/digest") == 0)
541 if (ascncasecmp(s
, "multipart/", 10) == 0)
543 if (asccasecmp(s
, "application/x-pkcs7-mime") == 0 ||
544 asccasecmp(s
, "application/pkcs7-mime") == 0)
550 * Get a mime style parameter from a header line.
553 mime_getparam(char *param
, char *h
)
560 if (!whitechar(*p
& 0377)) {
562 while (*p
&& (*p
!= ';' || c
== '\\')) {
563 c
= c
== '\\' ? '\0' : *p
;
570 while (whitechar(*p
& 0377))
572 if (ascncasecmp(p
, param
, sz
) == 0) {
574 while (whitechar(*p
& 0377))
580 while (*p
&& (*p
!= ';' || c
== '\\')) {
581 if (*p
== '"' && c
!= '\\') {
583 while (*p
&& (*p
!= '"' || c
== '\\')) {
584 c
= c
== '\\' ? '\0' : *p
;
589 c
= c
== '\\' ? '\0' : *p
;
596 while (whitechar(*p
& 0377))
602 if ((q
= strchr(p
, '"')) == NULL
)
606 while (*q
&& !whitechar(*q
& 0377) && *q
!= ';')
610 r
= salloc(q
- p
+ 1);
617 * Get the boundary out of a Content-Type: multipart/xyz header field.
620 mime_getboundary(char *h
)
625 if ((p
= mime_getparam("boundary", h
)) == NULL
)
630 memcpy(q
+ 2, p
, sz
);
631 *(q
+ sz
+ 2) = '\0';
636 * Get a line like "text/html html" and look if x matches the extension.
639 mime_tline(char *x
, char *l
)
644 if ((*l
& 0200) || alphachar(*l
& 0377) == 0)
647 while (blankchar(*l
& 0377) == 0 && *l
!= '\0')
652 while (blankchar(*l
& 0377) != 0 && *l
!= '\0')
658 while (whitechar(*l
& 0377) == 0 && *l
!= '\0')
662 if (strcmp(x
, n
) == 0) {
666 while (whitechar(*l
& 0377) != 0 && *l
!= '\0')
670 n
= salloc(strlen(type
) + 1);
678 * Check the given MIME type file for extension ext.
681 mime_type(char *ext
, char *filename
)
688 if ((f
= Fopen(filename
, "r")) == NULL
)
690 while (fgetline(&line
, &linesize
, NULL
, NULL
, f
, 0)) {
691 if ((type
= mime_tline(ext
, line
)) != NULL
)
701 * Return the Content-Type matching the extension of name.
704 mime_filecontent(char *name
)
708 if ((ext
= strrchr(name
, '.')) == NULL
|| *++ext
== '\0')
710 if ((content
= mime_type(ext
, expand(mimetypes_user
))) != NULL
)
712 if ((content
= mime_type(ext
, mimetypes_world
)) != NULL
)
718 * Check file contents.
720 static enum mimeclean
721 mime_isclean(FILE *f
)
724 unsigned curlen
= 1, maxlen
= 0, limit
= 950;
725 enum mimeclean isclean
= 0;
729 initial_pos
= ftell(f
);
734 if (c
== '\n' || c
== EOF
) {
736 * RFC 821 imposes a maximum line length of 1000
737 * characters including the terminating CRLF
738 * sequence. The configurable limit must not
739 * exceed that including a safety zone.
744 } else if (c
& 0200) {
745 isclean
|= MIME_HIGHBIT
;
746 } else if (c
== '\0') {
747 isclean
|= MIME_HASNUL
;
749 } else if ((c
< 040 && (c
!= '\t' && c
!= '\f')) || c
== 0177) {
750 isclean
|= MIME_CTRLCHAR
;
754 isclean
|= MIME_NOTERMNL
;
756 fseek(f
, initial_pos
, SEEK_SET
);
757 if ((cp
= value("maximum-unencoded-line-length")) != NULL
)
758 limit
= (unsigned)atoi(cp
);
762 isclean
|= MIME_LONGLINES
;
767 * Get the conversion that matches the encoding specified in the environment.
769 static enum conversion
770 gettextconversion(void)
775 if ((p
= value("encoding")) == NULL
)
777 if (equal(p
, "quoted-printable"))
779 else if (equal(p
, "8bit"))
782 fprintf(stderr
, catgets(catd
, CATSET
, 177,
783 "Warning: invalid encoding %s, using 8bit\n"), p
);
790 get_mime_convert(FILE *fp
, char **contenttype
, char **charset
,
791 enum mimeclean
*isclean
, int dosign
)
795 *isclean
= mime_isclean(fp
);
796 if (*isclean
& MIME_HASNUL
||
798 ascncasecmp(*contenttype
, "text/", 5))) {
799 convert
= CONV_TOB64
;
800 if (*contenttype
== NULL
||
801 ascncasecmp(*contenttype
, "text/", 5) == 0)
802 *contenttype
= "application/octet-stream";
804 } else if (*isclean
& (MIME_LONGLINES
|MIME_CTRLCHAR
|MIME_NOTERMNL
) ||
807 else if (*isclean
& MIME_HIGHBIT
)
808 convert
= gettextconversion();
811 if (*contenttype
== NULL
||
812 ascncasecmp(*contenttype
, "text/", 5) == 0) {
813 *charset
= getcharset(*isclean
);
814 if (wantcharset
== (char *)-1) {
815 *contenttype
= "application/octet-stream";
817 } if (*isclean
& MIME_CTRLCHAR
) {
818 convert
= CONV_TOB64
;
820 * RFC 2046 forbids control characters other than
821 * ^I or ^L in text/plain bodies. However, some
822 * obscure character sets actually contain these
823 * characters, so the content type can be set.
825 if ((*contenttype
= value("contenttype-cntrl")) == NULL
)
826 *contenttype
= "application/octet-stream";
827 } else if (*contenttype
== NULL
)
828 *contenttype
= "text/plain";
834 * Convert c to a hexadecimal character string and store it in hex.
837 ctohex(int c
, char *hex
)
843 hex
[1] = basetable
[d
];
845 hex
[0] = basetable
[(c
- d
) / 16];
847 hex
[0] = basetable
[0];
852 * Write to a file converting to quoted-printable.
853 * The mustquote function determines whether a character must be quoted.
856 mime_write_toqp(struct str
*in
, FILE *fo
, int (*mustquote
)(int))
858 char *p
, *upper
, *h
, hex
[3];
863 upper
= in
->s
+ in
->l
;
864 for (p
= in
->s
, l
= 0; p
< upper
; p
++) {
865 if (mustquote(*p
&0377) ||
866 (p
< upper
-1 && p
[1] == '\n' &&
867 blankchar(p
[0]&0377)) ||
868 (p
< upper
-4 && l
== 0 &&
869 p
[0] == 'F' && p
[1] == 'r' &&
870 p
[2] == 'o' && p
[3] == 'm') ||
871 (*p
== '.' && l
== 0 && p
< upper
-1 &&
875 fwrite("=\n", sizeof (char), 2, fo
);
880 h
= ctohex(*p
&0377, hex
);
881 fwrite(h
, sizeof *h
, 2, fo
);
888 fwrite("=\n", sizeof (char), 2, fo
);
899 * Write to a stringstruct converting to quoted-printable.
900 * The mustquote function determines whether a character must be quoted.
903 mime_str_toqp(struct str
*in
, struct str
*out
, int (*mustquote
)(int), int inhdr
)
907 out
->s
= smalloc(in
->l
* 3 + 1);
910 upper
= in
->s
+ in
->l
;
911 for (p
= in
->s
; p
< upper
; p
++) {
912 if (mustquote(*p
&0377) || (p
+1 < upper
&& *(p
+ 1) == '\n' &&
913 blankchar(*p
& 0377))) {
914 if (inhdr
&& *p
== ' ') {
930 * Write to a stringstruct converting from quoted-printable.
933 mime_fromqp(struct str
*in
, struct str
*out
, int ishdr
)
939 out
->s
= smalloc(out
->l
+ 1);
940 upper
= in
->s
+ in
->l
;
941 for (p
= in
->s
, q
= out
->s
; p
< upper
; p
++) {
946 } while (blankchar(*p
& 0377) && p
< upper
);
958 *q
= (char)strtol(quote
, NULL
, 16);
961 } else if (ishdr
&& *p
== '_')
969 #define mime_fromhdr_inc(inc) { \
970 size_t diff = q - out->s; \
971 out->s = srealloc(out->s, (maxstor += inc) + 1); \
972 q = &(out->s)[diff]; \
975 * Convert header fields from RFC 1522 format
978 mime_fromhdr(struct str
*in
, struct str
*out
, enum tdflags flags
)
980 char *p
, *q
, *op
, *upper
, *cs
, *cbeg
, *tcs
, *lastwordend
= NULL
;
981 struct str cin
, cout
;
983 size_t maxstor
, lastoutl
= 0;
985 iconv_t fhicd
= (iconv_t
)-1;
990 out
->s
= smalloc(maxstor
+ 1);
992 upper
= in
->s
+ in
->l
;
993 for (p
= in
->s
, q
= out
->s
; p
< upper
; p
++) {
995 if (*p
== '=' && *(p
+ 1) == '?') {
998 while (p
< upper
&& *p
!= '?')
999 p
++; /* strip charset */
1002 cs
= salloc(++p
- cbeg
);
1003 memcpy(cs
, cbeg
, p
- cbeg
- 1);
1004 cs
[p
- cbeg
- 1] = '\0';
1006 if (fhicd
!= (iconv_t
)-1)
1008 if (strcmp(cs
, tcs
))
1009 fhicd
= iconv_open_ft(tcs
, cs
);
1011 fhicd
= (iconv_t
)-1;
1015 convert
= CONV_FROMB64
;
1018 convert
= CONV_FROMQP
;
1020 default: /* invalid, ignore */
1030 if (*p
++ == '?' && *p
== '=')
1037 mime_fromb64(&cin
, &cout
, 1);
1040 mime_fromqp(&cin
, &cout
, 1);
1048 if ((flags
& TD_ICONV
) && fhicd
!= (iconv_t
)-1) {
1049 char *iptr
, *mptr
, *nptr
, *uptr
;
1050 size_t inleft
, outleft
;
1052 again
: inleft
= cout
.l
;
1053 outleft
= maxstor
- out
->l
;
1055 uptr
= nptr
+ outleft
;
1057 if (iconv_ft(fhicd
, &iptr
, &inleft
,
1058 &nptr
, &outleft
) == (size_t)-1 &&
1060 iconv(fhicd
, NULL
, NULL
, NULL
, NULL
);
1061 mime_fromhdr_inc(inleft
);
1065 * For state-dependent encodings,
1066 * reset the state here, assuming
1067 * that states are restricted to
1068 * single encoded-word parts.
1070 while (iconv(fhicd
, NULL
, NULL
,
1071 &nptr
, &outleft
) == (size_t)-1 &&
1073 mime_fromhdr_inc(16);
1074 out
->l
+= uptr
- mptr
- outleft
;
1075 q
+= uptr
- mptr
- outleft
;
1078 while (cout
.l
> maxstor
- out
->l
)
1079 mime_fromhdr_inc(cout
.l
-
1080 (maxstor
- out
->l
));
1081 memcpy(q
, cout
.s
, cout
.l
);
1093 while (out
->l
>= maxstor
)
1094 mime_fromhdr_inc(16);
1097 if (!blankchar(*p
&0377))
1103 if (flags
& TD_ISPR
) {
1105 makeprint(out
, &new);
1109 if (flags
& TD_DELCTRL
)
1110 out
->l
= delctrl(out
->s
, out
->l
);
1112 if (fhicd
!= (iconv_t
)-1)
1119 * Convert header fields to RFC 1522 format and write to the file fo.
1122 mime_write_tohdr(struct str
*in
, FILE *fo
)
1124 char *upper
, *wbeg
, *wend
, *charset
, *lastwordend
= NULL
, *lastspc
, b
,
1126 struct str cin
, cout
;
1127 size_t sz
= 0, col
= 0, wr
, charsetlen
, charset7len
;
1128 int quoteany
, mustquote
, broken
,
1129 maxcol
= 65 /* there is the header field's name, too */;
1131 upper
= in
->s
+ in
->l
;
1132 charset
= getcharset(MIME_HIGHBIT
);
1133 if ((charset7
= value("charset7")) == NULL
)
1134 charset7
= us_ascii
;
1135 charsetlen
= strlen(charset
);
1136 charset7len
= strlen(charset7
);
1137 charsetlen
= smax(charsetlen
, charset7len
);
1139 for (wbeg
= in
->s
, quoteany
= 0; wbeg
< upper
; wbeg
++) {
1141 if (mustquote_hdr(wbeg
, wbeg
== in
->s
, wbeg
== &upper
[-1]))
1144 if (2u * quoteany
> in
->l
) {
1146 * Print the entire field in base64.
1148 for (wbeg
= in
->s
; wbeg
< upper
; wbeg
= wend
) {
1152 cin
.l
= wend
- wbeg
;
1153 if (cin
.l
* 4/3 + 7 + charsetlen
1155 fprintf(fo
, "=?%s?B?",
1156 b
&0200 ? charset
: charset7
);
1157 wr
= mime_write_tob64(&cin
, fo
, 1);
1158 fwrite("?=", sizeof (char), 2, fo
);
1159 wr
+= 7 + charsetlen
;
1160 sz
+= wr
, col
+= wr
;
1162 fwrite("\n ", sizeof (char),
1182 * Print the field word-wise in quoted-printable.
1185 for (wbeg
= in
->s
; wbeg
< upper
; wbeg
= wend
) {
1187 while (wbeg
< upper
&& whitechar(*wbeg
& 0377)) {
1188 lastspc
= lastspc
? lastspc
: wbeg
;
1193 if (wbeg
== upper
) {
1195 while (lastspc
< wbeg
) {
1196 putc(*lastspc
&0377, fo
);
1205 wend
< upper
&& !whitechar(*wend
& 0377);
1208 if (mustquote_hdr(wend
, wend
== wbeg
,
1209 wbeg
== &upper
[-1]))
1212 if (mustquote
|| broken
||
1213 ((wend
- wbeg
) >= 74 && quoteany
)) {
1215 cin
.s
= lastwordend
? lastwordend
:
1217 cin
.l
= wend
- cin
.s
;
1218 mime_str_toqp(&cin
, &cout
,
1219 mustquote_inhdrq
, 1);
1220 if ((wr
= cout
.l
+ charsetlen
+ 7)
1223 while (lastspc
< wbeg
) {
1230 fprintf(fo
, "=?%s?Q?", b
&0200 ?
1231 charset
: charset7
);
1232 fwrite(cout
.s
, sizeof *cout
.s
,
1234 fwrite("?=", 1, 2, fo
);
1235 sz
+= wr
, col
+= wr
;
1245 if (lastspc
== NULL
) {
1261 (size_t)(wend
- wbeg
) > maxcol
- col
) {
1266 if (lastspc
== NULL
) {
1271 maxcol
-= wbeg
- lastspc
;
1274 while (lastspc
< wbeg
) {
1275 putc(*lastspc
&0377, fo
);
1278 wr
= fwrite(wbeg
, sizeof *wbeg
,
1280 sz
+= wr
, col
+= wr
;
1289 * Write len characters of the passed string to the passed file,
1290 * doing charset and header conversion.
1293 convhdra(char *str
, size_t len
, FILE *fp
)
1304 cbuf
= ac_alloc(cbufsz
= 1);
1306 if (iconvd
== (iconv_t
)-1) {
1316 if (iconv(iconvd
, &ip
, &isz
, &op
, &osz
) == (size_t)-1) {
1317 if (errno
!= E2BIG
) {
1321 cbuf
= ac_alloc(cbufsz
+= isz
);
1325 cin
.l
= cbufsz
- osz
;
1327 #endif /* HAVE_ICONV */
1328 sz
= mime_write_tohdr(&cin
, fp
);
1335 * Write an address to a header field.
1338 mime_write_tohdr_a(struct str
*in
, FILE *f
)
1343 in
->s
[in
->l
] = '\0';
1345 if ((cp
= routeaddr(in
->s
)) != NULL
&& cp
> lastcp
) {
1346 sz
+= convhdra(lastcp
, cp
- lastcp
, f
);
1350 for ( ; *cp
; cp
++) {
1353 sz
+= fwrite(lastcp
, 1, cp
- lastcp
+ 1, f
);
1355 cp
= skip_comment(cp
);
1357 sz
+= convhdra(lastcp
, cp
- lastcp
, f
);
1364 if (*cp
== '\\' && cp
[1])
1371 sz
+= fwrite(lastcp
, 1, cp
- lastcp
, f
);
1376 addstr(char **buf
, size_t *sz
, size_t *pos
, char *str
, size_t len
)
1378 *buf
= srealloc(*buf
, *sz
+= len
);
1379 memcpy(&(*buf
)[*pos
], str
, len
);
1384 addconv(char **buf
, size_t *sz
, size_t *pos
, char *str
, size_t len
)
1390 mime_fromhdr(&in
, &out
, TD_ISPR
|TD_ICONV
);
1391 addstr(buf
, sz
, pos
, out
.s
, out
.l
);
1396 * Interpret MIME strings in parts of an address field.
1399 mime_fromaddr(char *name
)
1403 size_t ressz
= 1, rescur
= 0;
1405 if (name
== NULL
|| *name
== '\0')
1407 if ((cp
= routeaddr(name
)) != NULL
&& cp
> name
) {
1408 addconv(&res
, &ressz
, &rescur
, name
, cp
- name
);
1412 for ( ; *cp
; cp
++) {
1415 addstr(&res
, &ressz
, &rescur
, lastcp
, cp
- lastcp
+ 1);
1417 cp
= skip_comment(cp
);
1419 addconv(&res
, &ressz
, &rescur
, lastcp
,
1427 if (*cp
== '\\' && cp
[1])
1434 addstr(&res
, &ressz
, &rescur
, lastcp
, cp
- lastcp
);
1442 * fwrite whilst adding prefix, if present.
1445 prefixwrite(void *ptr
, size_t size
, size_t nmemb
, FILE *f
,
1446 char *prefix
, size_t prefixlen
)
1449 static char lastc
= '\n';
1450 size_t lpref
, i
, qfold
= 0, lnlen
= 0, rsz
= size
* nmemb
, wsz
= 0;
1455 if (prefix
== NULL
) {
1457 lastc
= ((char *)ptr
)[rsz
- 1];
1458 return fwrite(ptr
, 1, rsz
, f
);
1461 if ((p
= value("quote-fold")) != NULL
) {
1462 qfold
= (size_t)strtol(p
, NULL
, 10);
1463 if (qfold
< prefixlen
+ 4)
1464 qfold
= prefixlen
+ 4;
1465 --qfold
; /* The newline escape */
1468 if (f
!= lastf
|| lastc
== '\n') {
1469 wsz
+= fwrite(prefix
, sizeof *prefix
, prefixlen
, f
);
1486 wsz
+= fwrite(prefix
, sizeof *prefix
, prefixlen
, f
);
1491 * After writing a real newline followed by our prefix,
1492 * compress the quoted prefixes
1494 for (lpref
= 0; p
!= maxp
;) {
1495 /* (c: keep cc happy) */
1496 for (c
= i
= 0; p
+ i
< maxp
;) {
1498 if (blankspacechar(c
)) /* XXX U+A0+ */
1509 jquoteok
: lnlen
+= lpref
;
1512 * Search forward until either *quote-fold* or NL.
1513 * In the former case try to break at whitespace,
1514 * but only if that lies in the 2nd half of the data
1516 for (c
= rsz
= i
= 0; p
+ i
< maxp
;) {
1522 if (lnlen
+ i
>= qfold
) {
1524 if (rsz
> qfold
>> 1)
1531 wsz
+= fwrite(p
, sizeof *p
, i
, f
);
1543 wsz
+= fwrite(prefix
, sizeof *prefix
, prefixlen
, f
);
1549 for (; i
> 0; ++wsz
, ++lnlen
, --i
)
1564 * fwrite while checking for displayability.
1567 fwrite_td(void *ptr
, size_t size
, size_t nmemb
, FILE *f
, enum tdflags flags
,
1568 char *prefix
, size_t prefixlen
)
1574 size_t inleft
, outleft
;
1576 char *mptr
, *xmptr
, *mlptr
= NULL
;
1579 csize
= size
* nmemb
;
1581 mptr
= xmptr
= ac_alloc(mptrsz
+ 1);
1583 if ((flags
& TD_ICONV
) && iconvd
!= (iconv_t
)-1) {
1584 again
: inleft
= csize
;
1588 if (iconv_ft(iconvd
, &iptr
, &inleft
, &nptr
, &outleft
) ==
1591 iconv(iconvd
, NULL
, NULL
, NULL
, NULL
);
1594 mptr
= ac_alloc(mptrsz
+ 1);
1597 nmemb
= mptrsz
- outleft
;
1598 size
= sizeof (char);
1600 csize
= size
* nmemb
;
1604 memcpy(mptr
, ptr
, csize
);
1606 upper
= mptr
+ csize
;
1608 if (flags
& TD_ISPR
) {
1612 makeprint(&in
, &out
);
1613 mptr
= mlptr
= out
.s
;
1616 if (flags
& TD_DELCTRL
)
1617 csize
= delctrl(mptr
, csize
);
1618 sz
= prefixwrite(mptr
, sizeof *mptr
, csize
, f
, prefix
, prefixlen
);
1625 * fwrite performing the given MIME conversion.
1628 mime_write(void *ptr
, size_t size
, FILE *f
,
1629 enum conversion convert
, enum tdflags dflags
,
1630 char *prefix
, size_t prefixlen
,
1631 char **restp
, size_t *restsizep
)
1637 char mptr
[LINESIZE
* 6];
1639 size_t inleft
, outleft
;
1646 if (csize
< sizeof mptr
&& (dflags
& TD_ICONV
)
1647 && iconvd
!= (iconv_t
)-1
1648 && (convert
== CONV_TOQP
|| convert
== CONV_8BIT
||
1649 convert
== CONV_TOB64
||
1650 convert
== CONV_TOHDR
)) {
1652 outleft
= sizeof mptr
;
1655 if (iconv(iconvd
, &iptr
, &inleft
,
1656 &nptr
, &outleft
) != (size_t)-1) {
1657 in
.l
= sizeof mptr
- outleft
;
1660 if (errno
== EILSEQ
|| errno
== EINVAL
)
1673 mime_fromqp(&in
, &out
, 0);
1674 sz
= fwrite_td(out
.s
, sizeof *out
.s
, out
.l
, f
, dflags
,
1679 sz
= mime_write_toqp(&in
, f
, mustquote_body
);
1682 sz
= prefixwrite(in
.s
, sizeof *in
.s
, in
.l
, f
,
1685 case CONV_FROMB64_T
:
1689 mime_fromb64_b(&in
, &out
, is_text
, f
);
1690 if (is_text
&& out
.s
[out
.l
-1] != '\n' && restp
&& restsizep
) {
1695 sz
= fwrite_td(out
.s
, sizeof *out
.s
, out
.l
, f
, dflags
,
1701 sz
= mime_write_tob64(&in
, f
, 0);
1704 mime_fromhdr(&in
, &out
, TD_ISPR
|TD_ICONV
);
1705 sz
= fwrite_td(out
.s
, sizeof *out
.s
, out
.l
, f
,
1706 dflags
&TD_DELCTRL
, prefix
, prefixlen
);
1710 sz
= mime_write_tohdr(&in
, f
);
1713 sz
= mime_write_tohdr_a(&in
, f
);
1716 sz
= fwrite_td(in
.s
, sizeof *in
.s
, in
.l
, f
, dflags
,