1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ MIME support functions.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
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 #define _CHARSET() ((_cs_iter != NULL) ? _cs_iter : charset_get_8bit())
45 struct mtnode
*mt_next
;
46 size_t mt_mtlen
; /* Length of MIME type string */
47 char mt_line
[VFIELD_SIZE(8)];
50 static char const *const _mt_sources
[] = {
51 /* XXX Order fixed due to *mimetypes-load-control* handling! */
52 MIME_TYPES_USR
, MIME_TYPES_SYS
, NULL
54 *const _mt_bltin
[] = {
55 #include "mime_types.h"
59 struct mtnode
*_mt_list
;
60 char *_cs_iter_base
, *_cs_iter
;
62 /* Initialize MIME type list */
63 static void _mt_init(void);
64 static void __mt_add_line(char const *line
, struct mtnode
**tail
);
66 /* Get the conversion that matches *encoding* */
67 static enum conversion
_conversion_by_encoding(void);
69 /* fwrite(3) while checking for displayability */
70 static ssize_t
_fwrite_td(struct str
const *input
, enum tdflags flags
,
71 struct str
*rest
, struct quoteflt
*qf
);
73 static size_t delctrl(char *cp
, size_t sz
);
74 static int has_highbit(register const char *s
);
75 static int is_this_enc(const char *line
, const char *encoding
);
76 static size_t mime_write_tohdr(struct str
*in
, FILE *fo
);
77 static size_t convhdra(char const *str
, size_t len
, FILE *fp
);
78 static size_t mime_write_tohdr_a(struct str
*in
, FILE *f
);
79 static void addstr(char **buf
, size_t *sz
, size_t *pos
,
80 char const *str
, size_t len
);
81 static void addconv(char **buf
, size_t *sz
, size_t *pos
,
82 char const *str
, size_t len
);
87 struct mtnode
*tail
= NULL
;
91 char const *ccp
, *const*srcs
;
94 if ((ccp
= value("mimetypes-load-control")) == NULL
)
96 else for (idx_ok
= 0; *ccp
!= '\0'; ++ccp
)
107 /* XXX bad *mimetypes-load-control*; log error? */
111 for (idx
= 1, srcs
= _mt_sources
; *srcs
!= NULL
; idx
<<= 1, ++srcs
) {
112 if ((idx
& idx_ok
) == 0 || (ccp
= file_expand(*srcs
)) == NULL
)
114 if ((fp
= Fopen(ccp
, "r")) == NULL
) {
115 /*fprintf(stderr, tr(176, "Cannot open %s\n"), fn);*/
118 while (fgetline(&line
, &linesize
, NULL
, NULL
, fp
, 0))
119 __mt_add_line(line
, &tail
);
125 for (srcs
= _mt_bltin
; *srcs
!= NULL
; ++srcs
)
126 __mt_add_line(*srcs
, &tail
);
130 __mt_add_line(char const *line
, struct mtnode
**tail
) /* XXX diag? dups!*/
136 if (! alphachar(*line
))
140 while (blankchar(*line
) == 0 && *line
!= '\0')
144 tlen
= (size_t)(line
- typ
);
146 while (blankchar(*line
) != 0 && *line
!= '\0')
152 if (line
[elen
- 1] == '\n' && line
[--elen
] == '\0')
155 mtn
= smalloc(sizeof(struct mtnode
) -
156 VFIELD_SIZEOF(struct mtnode
, mt_line
) +
157 tlen
+ 1 + elen
+ 1);
159 (*tail
)->mt_next
= mtn
;
164 mtn
->mt_mtlen
= tlen
;
165 memcpy(mtn
->mt_line
, typ
, tlen
);
166 mtn
->mt_line
[tlen
] = '\0';
168 memcpy(mtn
->mt_line
+ tlen
, line
, elen
);
170 mtn
->mt_line
[tlen
] = '\0';
174 static enum conversion
175 _conversion_by_encoding(void)
180 if ((cp
= value("encoding")) == NULL
)
181 ret
= MIME_DEFAULT_ENCODING
;
182 else if (strcmp(cp
, "quoted-printable") == 0)
184 else if (strcmp(cp
, "8bit") == 0)
186 else if (strcmp(cp
, "base64") == 0)
189 fprintf(stderr
, tr(177,
190 "Warning: invalid encoding %s, using base64\n"), cp
);
197 _fwrite_td(struct str
const *input
, enum tdflags flags
, struct str
*rest
,
200 /* TODO note: after send/MIME layer rewrite we will have a string pool
201 * TODO so that memory allocation count drops down massively; for now,
202 * TODO v14.* that is, we pay a lot & heavily depend on the allocator */
203 /* TODO well if we get a broken pipe here, and it happens to
204 * TODO happen pretty easy when sleeping in a full pipe buffer,
205 * TODO then the current codebase performs longjump away;
206 * TODO this leaves memory leaks behind ('think up to 3 per,
207 * TODO dep. upon alloca availability). For this to be fixed
208 * TODO we either need to get rid of the longjmp()s (tm) or
209 * TODO the storage must come from the outside or be tracked
210 * TODO in a carrier struct. Best both. But storage reuse
211 * TODO would be a bigbig win besides */
212 /* *input* _may_ point to non-modifyable buffer; but even then it only
213 * needs to be dup'ed away if we have to transform the content */
223 if ((flags
& TD_ICONV
) && iconvd
!= (iconv_t
)-1) {
226 if (rest
!= NULL
&& rest
->l
> 0) {
227 in
.l
= rest
->l
+ input
->l
;
228 in
.s
= buf
= smalloc(in
.l
+ 1);
229 memcpy(in
.s
, rest
->s
, rest
->l
);
230 memcpy(in
.s
+ rest
->l
, input
->s
, input
->l
);
234 if (n_iconv_str(iconvd
, &out
, &in
, &in
, TRU1
) != 0 &&
235 rest
!= NULL
&& in
.l
> 0) {
236 /* Incomplete multibyte at EOF is special */
237 if (flags
& _TD_EOF
) {
238 out
.s
= srealloc(out
.s
, out
.l
+ 4);
239 /* TODO 0xFFFD out.s[out.l++] = '[';*/
240 out
.s
[out
.l
++] = '?'; /* TODO 0xFFFD !!! */
241 /* TODO 0xFFFD out.s[out.l++] = ']';*/
243 (void)n_str_add(rest
, &in
);
247 flags
&= ~_TD_BUFCOPY
;
255 makeprint(&in
, &out
);
256 else if (flags
& _TD_BUFCOPY
)
257 n_str_dup(&out
, &in
);
260 if (flags
& TD_DELCTRL
)
261 out
.l
= delctrl(out
.s
, out
.l
);
263 rv
= quoteflt_push(qf
, out
.s
, out
.l
);
267 if (in
.s
!= input
->s
)
273 delctrl(char *cp
, size_t sz
)
278 if (!cntrlchar(cp
[x
]&0377))
286 has_highbit(const char *s
)
292 while (*s
++ != '\0');
298 name_highbit(struct name
*np
)
301 if (has_highbit(np
->n_name
) || has_highbit(np
->n_fullname
))
309 charset_get_7bit(void)
313 if ((t
= value("charset-7bit")) == NULL
)
319 charset_get_8bit(void)
323 if ((t
= value(CHARSET_8BIT_VAR
)) == NULL
)
333 if ((t
= value("ttycharset")) == NULL
)
339 charset_iter_reset(char const *a_charset_to_try_first
)
342 size_t sarrl
[3], len
;
345 sarr
[0] = a_charset_to_try_first
;
347 if ((sarr
[1] = value("sendcharsets")) == NULL
&&
348 value("sendcharsets-else-ttycharset"))
349 sarr
[1] = charset_get_lc();
351 sarr
[2] = charset_get_8bit();
353 sarrl
[2] = len
= strlen(sarr
[2]);
355 if ((cp
= UNCONST(sarr
[1])) != NULL
)
356 len
+= (sarrl
[1] = strlen(cp
));
359 if ((cp
= UNCONST(sarr
[0])) != NULL
)
360 len
+= (sarrl
[0] = strlen(cp
));
365 _cs_iter_base
= cp
= salloc(len
+ 1);
368 if ((len
= sarrl
[0]) != 0) {
369 memcpy(cp
, sarr
[0], len
);
373 if ((len
= sarrl
[1]) != 0) {
374 memcpy(cp
, sarr
[1], len
);
380 memcpy(cp
, sarr
[2], len
);
386 charset_iter_next(void)
388 return (_cs_iter
= strcomma(&_cs_iter_base
, 1));
392 charset_iter_current(void)
398 charset_iter_recurse(char *outer_storage
[2]) /* TODO LEGACY FUN, REMOVE */
400 outer_storage
[0] = _cs_iter_base
;
401 outer_storage
[1] = _cs_iter
;
405 charset_iter_restore(char *outer_storage
[2]) /* TODO LEGACY FUN, REMOVE */
407 _cs_iter_base
= outer_storage
[0];
408 _cs_iter
= outer_storage
[1];
412 need_hdrconv(struct header
*hp
, enum gfield w
)
414 char const *ret
= NULL
;
417 if (hp
->h_from
!= NULL
) {
418 if (name_highbit(hp
->h_from
))
420 } else if (has_highbit(myaddrs(NULL
)))
422 if (hp
->h_organization
) {
423 if (has_highbit(hp
->h_organization
))
425 } else if (has_highbit(value("ORGANIZATION")))
428 if (name_highbit(hp
->h_replyto
))
430 } else if (has_highbit(value("replyto")))
433 if (name_highbit(hp
->h_sender
))
435 } else if (has_highbit(value("sender")))
438 if (w
& GTO
&& name_highbit(hp
->h_to
))
440 if (w
& GCC
&& name_highbit(hp
->h_cc
))
442 if (w
& GBCC
&& name_highbit(hp
->h_bcc
))
444 if (w
& GSUBJECT
&& has_highbit(hp
->h_subject
))
445 jneeds
: ret
= _CHARSET();
450 is_this_enc(const char *line
, const char *encoding
)
456 while (*line
&& *encoding
)
457 if (c
= *line
++, lowerconv(c
) != *encoding
++)
459 if (quoted
&& *line
== '"')
461 if (*line
== '\0' || whitechar(*line
& 0377))
467 * Get the mime encoding from a Content-Transfer-Encoding header field.
472 if (is_this_enc(p
, "7bit"))
474 if (is_this_enc(p
, "8bit"))
476 if (is_this_enc(p
, "base64"))
478 if (is_this_enc(p
, "binary"))
480 if (is_this_enc(p
, "quoted-printable"))
486 * Get a mime style parameter from a header line.
489 mime_getparam(char const *param
, char *h
)
496 if (!whitechar(*p
& 0377)) {
498 while (*p
&& (*p
!= ';' || c
== '\\')) {
499 c
= c
== '\\' ? '\0' : *p
;
506 while (whitechar(*p
& 0377))
508 if (ascncasecmp(p
, param
, sz
) == 0) {
510 while (whitechar(*p
& 0377))
516 while (*p
&& (*p
!= ';' || c
== '\\')) {
517 if (*p
== '"' && c
!= '\\') {
519 while (*p
&& (*p
!= '"' || c
== '\\')) {
520 c
= c
== '\\' ? '\0' : *p
;
525 c
= c
== '\\' ? '\0' : *p
;
532 while (whitechar(*p
& 0377))
537 if ((q
= strchr(p
, '"')) == NULL
)
540 while (*q
&& !whitechar(*q
& 0377) && *q
!= ';')
544 r
= salloc(q
- p
+ 1);
551 mime_get_boundary(char *h
, size_t *len
)
556 if ((p
= mime_getparam("boundary", h
)) != NULL
) {
562 memcpy(q
+ 2, p
, sz
);
563 *(q
+ sz
+ 2) = '\0';
569 mime_create_boundary(void)
574 snprintf(bp
, 48, "=_%011lu=-%s=_",
575 (ul_it
)time_current
.tc_time
, getrandstring(47 - (11 + 6)));
580 mime_classify_file(FILE *fp
, char const **contenttype
, char const **charset
,
583 /* TODO classify once only PLEASE PLEASE PLEASE */
584 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
585 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
586 * TODO and report that state to the outer world */
588 #define F_SIZEOF (sizeof(F_) - 1)
590 char f_buf
[F_SIZEOF
], *f_p
= f_buf
;
591 enum { _CLEAN
= 0, /* Plain RFC 2822 message */
592 _NCTT
= 1<<0, /* *contenttype == NULL */
593 _ISTXT
= 1<<1, /* *contenttype =~ text/ */
594 _ISTXTCOK
= 1<<2, /* _ISTXT+*mime-allow-text-controls* */
595 _HIGHBIT
= 1<<3, /* Not 7bit clean */
596 _LONGLINES
= 1<<4, /* MIME_LINELEN_LIMIT exceed. */
597 _CTRLCHAR
= 1<<5, /* Control characters seen */
598 _HASNUL
= 1<<6, /* Contains \0 characters */
599 _NOTERMNL
= 1<<7, /* Lacks a final newline */
600 _TRAILWS
= 1<<8, /* Blanks before NL */
601 _FROM_
= 1<<9 /* ^From_ seen */
603 enum conversion convert
;
607 assert(ftell(fp
) == 0x0l
);
611 if (*contenttype
== NULL
)
613 else if (ascncasecmp(*contenttype
, "text/", 5) == 0)
614 ctt
= value("mime-allow-text-controls")
615 ? _ISTXT
| _ISTXTCOK
: _ISTXT
;
616 convert
= _conversion_by_encoding();
621 /* We have to inspect the file content */
622 for (curlen
= 0, c
= EOF
;; ++curlen
) {
628 if ((ctt
& _ISTXTCOK
) == 0)
632 if (c
== '\n' || c
== EOF
) {
633 if (curlen
>= MIME_LINELEN_LIMIT
)
637 if (blankchar(lastc
))
644 * A bit hairy is handling of \r=\x0D=CR.
645 * RFC 2045, 6.7: Control characters other than TAB, or
646 * CR and LF as parts of CRLF pairs, must not appear.
647 * \r alone does not force _CTRLCHAR below since we cannot peek
648 * the next character.
649 * Thus right here, inspect the last seen character for if its
650 * \r and set _CTRLCHAR in a delayed fashion
652 /*else*/ if (lastc
== '\r')
655 /* Control character? */
656 if (c
< 0x20 || c
== 0x7F) {
657 /* RFC 2045, 6.7, as above ... */
658 if (c
!= '\t' && c
!= '\r')
661 * If there is a escape sequence in backslash notation
662 * defined for this in ANSI X3.159-1989 (ANSI C89),
663 * don't treat it as a control for real.
664 * I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT.
665 * Don't follow libmagic(1) in respect to \v=\x0B=VT.
666 * \f=\x0C=NP; do ignore \e=\x1B=ESC.
668 if ((c
>= '\x07' && c
<= '\x0D') || c
== '\x1B')
670 ctt
|= _HASNUL
; /* Force base64 */
671 if ((ctt
& _ISTXTCOK
) == 0)
673 } else if (c
& 0x80) {
675 /* TODO count chars with HIGHBIT? libmagic?
676 * TODO try encode part - base64 if bails? */
677 if ((ctt
& (_NCTT
|_ISTXT
)) == 0) { /* TODO _NCTT?? */
678 ctt
|= _HASNUL
; /* Force base64 */
681 } else if ((ctt
& _FROM_
) == 0 && curlen
< (sl_it
)F_SIZEOF
) {
683 if (curlen
== (sl_it
)(F_SIZEOF
- 1) &&
684 (size_t)(f_p
- f_buf
) == F_SIZEOF
&&
685 memcmp(f_buf
, F_
, F_SIZEOF
) == 0)
694 convert
= CONV_TOB64
;
695 /* Don't overwrite a text content-type to allow UTF-16 and
696 * such, but only on request;
697 * Otherwise enforce what file(1)/libmagic(3) would suggest */
700 if (ctt
& (_NCTT
|_ISTXT
))
701 *contenttype
= "application/octet-stream";
702 if (*charset
== NULL
)
707 if (ctt
& (_LONGLINES
|_CTRLCHAR
|_NOTERMNL
|_TRAILWS
|_FROM_
)) {
711 if (ctt
& _HIGHBIT
) {
712 jstepi
: if (ctt
& (_NCTT
|_ISTXT
))
713 *do_iconv
= (ctt
& _HIGHBIT
) != 0;
715 j7bit
: convert
= CONV_7BIT
;
717 *contenttype
= "text/plain";
719 /* Not an attachment with specified charset? */
721 if (*charset
== NULL
)
722 *charset
= (ctt
& _HIGHBIT
) ? _CHARSET() : charset_get_7bit();
730 mime_classify_content_of_part(struct mimepart
const *mip
)
732 enum mimecontent mc
= MIME_UNKNOWN
;
733 char const *ct
= mip
->m_ct_type_plain
;
735 if (asccasecmp(ct
, "application/octet-stream") == 0 &&
736 mip
->m_filename
!= NULL
&&
737 value("mime-counter-evidence")) {
738 ct
= mime_classify_content_type_by_fileext(mip
->m_filename
);
740 /* TODO how about let *mime-counter-evidence* have
741 * TODO a value, and if set, saving the attachment in
742 * TODO a temporary file that mime_classify_file() can
743 * TODO examine, and using MIME_TEXT if that gives us
744 * TODO something that seems to be human readable?! */
747 if (strchr(ct
, '/') == NULL
) /* For compatibility with non-MIME */
749 else if (asccasecmp(ct
, "text/plain") == 0)
750 mc
= MIME_TEXT_PLAIN
;
751 else if (asccasecmp(ct
, "text/html") == 0)
753 else if (ascncasecmp(ct
, "text/", 5) == 0)
755 else if (asccasecmp(ct
, "message/rfc822") == 0)
757 else if (ascncasecmp(ct
, "message/", 8) == 0)
759 else if (asccasecmp(ct
, "multipart/alternative") == 0)
760 mc
= MIME_ALTERNATIVE
;
761 else if (asccasecmp(ct
, "multipart/digest") == 0)
763 else if (ascncasecmp(ct
, "multipart/", 10) == 0)
765 else if (asccasecmp(ct
, "application/x-pkcs7-mime") == 0 ||
766 asccasecmp(ct
, "application/pkcs7-mime") == 0)
773 mime_classify_content_type_by_fileext(char const *name
)
775 char *content
= NULL
;
779 if ((name
= strrchr(name
, '.')) == NULL
|| *++name
== '\0')
782 if (_mt_list
== NULL
)
786 for (mtn
= _mt_list
; mtn
!= NULL
; mtn
= mtn
->mt_next
) {
787 char const *ext
= mtn
->mt_line
+ mtn
->mt_mtlen
+ 1,
790 while (! whitechar(*cp
) && *cp
!= '\0')
792 /* Better to do case-insensitive comparison on
793 * extension, since the RFC doesn't specify case of
794 * attribute values? */
795 if (nlen
== (size_t)(cp
- ext
) &&
796 ascncasecmp(name
, ext
, nlen
) == 0) {
797 content
= savestrbuf(mtn
->mt_line
,
801 while (whitechar(*cp
) && *cp
!= '\0')
804 } while (*ext
!= '\0');
820 if (asccasecmp(*argv
, "show") == 0)
822 if (asccasecmp(*argv
, "clear") == 0)
825 fprintf(stderr
, "Synopsis: mimetypes: %s\n", tr(418,
826 "Either <show> (default) or <clear> the mime.types cache"));
829 return (v
== NULL
? STOP
: OKAY
);
836 if (_mt_list
== NULL
)
839 if ((fp
= Ftemp(&cp
, "Ra", "w+", 0600, 1)) == NULL
) {
847 for (l
= 0, mtn
= _mt_list
; mtn
!= NULL
; ++l
, mtn
= mtn
->mt_next
)
848 fprintf(fp
, "%s\t%s\n", mtn
->mt_line
,
849 mtn
->mt_line
+ mtn
->mt_mtlen
+ 1);
851 page_or_print(fp
, l
);
857 while ((mtn
= _mt_list
) != NULL
) {
858 _mt_list
= mtn
->mt_next
;
865 * Convert header fields from RFC 1522 format
866 * TODO mime_fromhdr(): NO error handling, fat; REWRITE **ASAP**
869 mime_fromhdr(struct str
const *in
, struct str
*out
, enum tdflags flags
)
871 /* TODO mime_fromhdr(): is called with strings that contain newlines;
872 * TODO this is the usual newline problem all around the codebase;
873 * TODO i.e., if we strip it, then the display misses it ;} */
874 struct str cin
, cout
;
875 char *p
, *op
, *upper
, *cs
, *cbeg
;
877 size_t lastoutl
= (size_t)-1;
880 iconv_t fhicd
= (iconv_t
)-1;
885 *(out
->s
= smalloc(1)) = '\0';
891 tcs
= charset_get_lc();
898 if (*p
== '=' && *(p
+ 1) == '?') {
901 while (p
< upper
&& *p
!= '?')
902 p
++; /* strip charset */
905 cs
= salloc(++p
- cbeg
);
906 memcpy(cs
, cbeg
, p
- cbeg
- 1);
907 cs
[p
- cbeg
- 1] = '\0';
909 if (fhicd
!= (iconv_t
)-1)
910 n_iconv_close(fhicd
);
911 if (asccasecmp(cs
, tcs
) != 0)
912 fhicd
= n_iconv_open(tcs
, cs
);
918 convert
= CONV_FROMB64
;
921 convert
= CONV_FROMQP
;
923 default: /* invalid, ignore */
933 if (*p
++ == '?' && *p
== '=')
942 if (convert
== CONV_FROMB64
) {
943 /* XXX Take care for, and strip LF from
944 * XXX [Invalid Base64 encoding ignored] */
945 if (b64_decode(&cout
, &cin
, NULL
) == STOP
&&
946 cout
.s
[cout
.l
- 1] == '\n')
949 (void)qp_decode(&cout
, &cin
, NULL
);
950 if (lastoutl
!= (size_t)-1)
953 if ((flags
& TD_ICONV
) && fhicd
!= (iconv_t
)-1) {
954 cin
.s
= NULL
, cin
.l
= 0; /* XXX string pool ! */
955 convert
= n_iconv_str(fhicd
, &cin
, &cout
,
957 out
= n_str_add(out
, &cin
);
958 if (convert
) /* EINVAL at EOS */
959 out
= n_str_add_buf(out
, "?", 1);
963 out
= n_str_add(out
, &cout
);
973 while ((op
= p
+ convert
) < upper
&&
974 (op
[0] != '=' || op
[1] != '?'))
976 out
= n_str_add_buf(out
, p
, convert
);
978 if (! blankchar(p
[-1]))
979 lastoutl
= (size_t)-1;
982 out
->s
[out
->l
] = '\0';
984 if (flags
& TD_ISPR
) {
985 makeprint(out
, &cout
);
989 if (flags
& TD_DELCTRL
)
990 out
->l
= delctrl(out
->s
, out
->l
);
992 if (fhicd
!= (iconv_t
)-1)
993 n_iconv_close(fhicd
);
1000 * Convert header fields to RFC 1522 format and write to the file fo.
1003 mime_write_tohdr(struct str
*in
, FILE *fo
) /* TODO rewrite - FAST! */
1005 struct str cin
, cout
;
1006 char buf
[B64_LINESIZE
];
1007 char const *charset7
, *charset
, *upper
, *wbeg
, *wend
, *lastspc
,
1008 *lastwordend
= NULL
;
1009 size_t sz
= 0, col
= 0, quoteany
, wr
, charsetlen
,
1010 maxcol
= 65 /* there is the header field's name, too */;
1011 bool_t highbit
, mustquote
, broken
;
1013 charset7
= charset_get_7bit();
1014 charset
= _CHARSET();
1015 wr
= strlen(charset7
);
1016 charsetlen
= strlen(charset
);
1017 charsetlen
= MAX(charsetlen
, wr
);
1018 upper
= in
->s
+ in
->l
;
1020 /* xxx note this results in too much hits since =/? force quoting even
1021 * xxx if they don't form =? etc. */
1022 quoteany
= mime_cte_mustquote(in
->s
, in
->l
, TRU1
);
1026 for (wbeg
= in
->s
; wbeg
< upper
; ++wbeg
)
1027 if ((uc_it
)*wbeg
& 0x80)
1030 if (quoteany
<< 1 > in
->l
) {
1032 * Print the entire field in base64.
1034 for (wbeg
= in
->s
; wbeg
< upper
; wbeg
= wend
) {
1036 cin
.s
= UNCONST(wbeg
);
1038 cin
.l
= wend
- wbeg
;
1039 if (cin
.l
* 4/3 + 7 + charsetlen
1042 cout
.l
= sizeof buf
;
1043 wr
= fprintf(fo
, "=?%s?B?%s?=",
1044 highbit
? charset
: charset7
,
1045 b64_encode(&cout
, &cin
, B64_BUF
1047 sz
+= wr
, col
+= wr
;
1049 fwrite("\n ", sizeof (char),
1069 * Print the field word-wise in quoted-printable.
1072 for (wbeg
= in
->s
; wbeg
< upper
; wbeg
= wend
) {
1074 while (wbeg
< upper
&& whitechar(*wbeg
)) {
1075 lastspc
= lastspc
? lastspc
: wbeg
;
1080 if (wbeg
== upper
) {
1082 while (lastspc
< wbeg
) {
1083 putc(*lastspc
&0377, fo
);
1090 if (lastspc
!= NULL
)
1093 for (wend
= wbeg
; wend
< upper
&& ! whitechar(*wend
);
1095 if ((uc_it
)*wend
& 0x80)
1097 mustquote
= (mime_cte_mustquote(wbeg
,
1098 (size_t)(wend
- wbeg
), TRU1
) != 0);
1100 if (mustquote
|| broken
||
1101 ((wend
- wbeg
) >= 76-5 && quoteany
)) {
1102 for (cout
.s
= NULL
;;) {
1103 cin
.s
= UNCONST(lastwordend
?
1104 lastwordend
: wbeg
);
1105 cin
.l
= wend
- cin
.s
;
1106 (void)qp_encode(&cout
, &cin
, QP_ISHEAD
);
1107 wr
= cout
.l
+ charsetlen
+ 7;
1109 if (col
<= maxcol
&&
1110 wr
<= maxcol
- col
) {
1112 /* TODO because we inc-
1113 * TODO luded the WS in
1114 * TODO the encoded str,
1115 * TODO put SP only??
1117 * 'linear-white-space'
1119 * a pair of adjacent
1120 * 'encoded-word's is
1126 fprintf(fo
, "=?%s?Q?%.*s?=",
1129 (int)cout
.l
, cout
.s
);
1130 sz
+= wr
, col
+= wr
;
1132 } else if (col
> 1) {
1133 /* TODO assuming SP separator,
1134 * TODO ignore *lastspc* !?? */
1136 if (lastspc
!= NULL
) {
1149 for (;;) { /* XXX */
1151 assert(wend
> wbeg
);
1152 if (wr
- 4 < maxcol
)
1163 (size_t)(wend
- wbeg
) > maxcol
- col
) {
1168 if (lastspc
== NULL
) {
1173 maxcol
-= wbeg
- lastspc
;
1176 while (lastspc
< wbeg
) {
1177 putc(*lastspc
&0377, fo
);
1180 wr
= fwrite(wbeg
, sizeof *wbeg
,
1182 sz
+= wr
, col
+= wr
;
1191 * Write len characters of the passed string to the passed file,
1192 * doing charset and header conversion.
1195 convhdra(char const *str
, size_t len
, FILE *fp
)
1203 cin
.s
= UNCONST(str
);
1207 if (iconvd
!= (iconv_t
)-1) {
1209 if (n_iconv_str(iconvd
, &ciconv
, &cin
, NULL
, FAL0
) != 0)
1214 ret
= mime_write_tohdr(&cin
, fp
);
1217 if (ciconv
.s
!= NULL
)
1224 * Write an address to a header field.
1227 mime_write_tohdr_a(struct str
*in
, FILE *f
)
1229 char const *cp
, *lastcp
;
1232 in
->s
[in
->l
] = '\0';
1234 if ((cp
= routeaddr(in
->s
)) != NULL
&& cp
> lastcp
) {
1235 sz
+= convhdra(lastcp
, cp
- lastcp
, f
);
1239 for ( ; *cp
; cp
++) {
1242 sz
+= fwrite(lastcp
, 1, cp
- lastcp
+ 1, f
);
1244 cp
= skip_comment(cp
);
1246 sz
+= convhdra(lastcp
, cp
- lastcp
, f
);
1253 if (*cp
== '\\' && cp
[1])
1260 sz
+= fwrite(lastcp
, 1, cp
- lastcp
, f
);
1265 addstr(char **buf
, size_t *sz
, size_t *pos
, char const *str
, size_t len
)
1267 *buf
= srealloc(*buf
, *sz
+= len
);
1268 memcpy(&(*buf
)[*pos
], str
, len
);
1273 addconv(char **buf
, size_t *sz
, size_t *pos
, char const *str
, size_t len
)
1277 in
.s
= UNCONST(str
);
1279 mime_fromhdr(&in
, &out
, TD_ISPR
|TD_ICONV
);
1280 addstr(buf
, sz
, pos
, out
.s
, out
.l
);
1285 * Interpret MIME strings in parts of an address field.
1288 mime_fromaddr(char const *name
)
1290 char const *cp
, *lastcp
;
1292 size_t ressz
= 1, rescur
= 0;
1297 return savestr(name
);
1298 if ((cp
= routeaddr(name
)) != NULL
&& cp
> name
) {
1299 addconv(&res
, &ressz
, &rescur
, name
, cp
- name
);
1303 for ( ; *cp
; cp
++) {
1306 addstr(&res
, &ressz
, &rescur
, lastcp
, cp
- lastcp
+ 1);
1308 cp
= skip_comment(cp
);
1310 addconv(&res
, &ressz
, &rescur
, lastcp
,
1318 if (*cp
== '\\' && cp
[1])
1325 addstr(&res
, &ressz
, &rescur
, lastcp
, cp
- lastcp
);
1326 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1339 xmime_write(char const *ptr
, size_t size
, FILE *f
, enum conversion convert
,
1340 enum tdflags dflags
, struct str
*rest
)
1343 struct quoteflt
*qf
;
1345 quoteflt_reset(qf
= quoteflt_dummy(), f
);
1346 rv
= mime_write(ptr
, size
, f
, convert
, dflags
, qf
, rest
);
1347 assert(quoteflt_flush(qf
) == 0);
1352 mime_write(char const *ptr
, size_t size
, FILE *f
,
1353 enum conversion convert
, enum tdflags dflags
,
1354 struct quoteflt
*qf
, struct str
*rest
)
1356 /* TODO note: after send/MIME layer rewrite we will have a string pool
1357 * TODO so that memory allocation count drops down massively; for now,
1358 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1363 in
.s
= UNCONST(ptr
);
1368 dflags
|= _TD_BUFCOPY
;
1369 if ((sz
= size
) == 0) {
1370 if (rest
!= NULL
&& rest
->l
!= 0)
1376 if ((dflags
& TD_ICONV
) && iconvd
!= (iconv_t
)-1 &&
1377 (convert
== CONV_TOQP
|| convert
== CONV_8BIT
||
1378 convert
== CONV_TOB64
|| convert
== CONV_TOHDR
)) {
1379 if (n_iconv_str(iconvd
, &out
, &in
, NULL
, FAL0
) != 0) {
1380 /* XXX report conversion error? */;
1386 dflags
&= ~_TD_BUFCOPY
;
1393 state
= qp_decode(&out
, &in
, rest
);
1396 (void)qp_encode(&out
, &in
, QP_NONE
);
1399 sz
= quoteflt_push(qf
, in
.s
, in
.l
);
1404 case CONV_FROMB64_T
:
1405 state
= b64_decode(&out
, &in
, rest
);
1407 if ((sz
= out
.l
) != 0) {
1408 size_t opl
= qf
->qf_pfix_len
;
1410 qf
->qf_pfix_len
= 0;
1411 sz
= _fwrite_td(&out
, (dflags
& ~_TD_BUFCOPY
), rest
,qf
);
1412 qf
->qf_pfix_len
= opl
;
1418 (void)b64_encode(&out
, &in
, B64_LF
|B64_MULTILINE
);
1420 sz
= fwrite(out
.s
, sizeof *out
.s
, out
.l
, f
);
1421 if (sz
!= (ssize_t
)out
.l
)
1425 mime_fromhdr(&in
, &out
,
1426 TD_ISPR
| TD_ICONV
| (dflags
& TD_DELCTRL
));
1427 sz
= quoteflt_push(qf
, out
.s
, out
.l
);
1430 sz
= mime_write_tohdr(&in
, f
);
1433 sz
= mime_write_tohdr_a(&in
, f
);
1436 sz
= _fwrite_td(&in
, dflags
, NULL
, qf
);