1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ MIME support functions.
3 *@ TODO Complete rewrite.
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 * SPDX-License-Identifier: BSD-4-Clause
11 * Gunnar Ritter. All rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. All advertising materials mentioning features or use of this software
22 * must display the following acknowledgement:
23 * This product includes software developed by Gunnar Ritter
24 * and his contributors.
25 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
26 * may be used to endorse or promote products derived from this software
27 * without specific prior written permission.
29 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
30 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
33 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
45 #ifndef mx_HAVE_AMALGAMATION
53 /* TODO nonsense (should be filter chain!) */
54 #include "mx/filter-quote.h"
58 #include "mx/ui-str.h"
61 #include "su/code-in.h"
63 /* Don't ask, but it keeps body and soul together */
64 enum a_mime_structure_hack
{
70 static char *_cs_iter_base
, *_cs_iter
;
72 # define _CS_ITER_GET() \
73 ((_cs_iter != NULL) ? _cs_iter : ok_vlook(CHARSET_8BIT_OKEY))
75 # define _CS_ITER_GET() ((_cs_iter != NULL) ? _cs_iter : ok_vlook(ttycharset))
77 #define _CS_ITER_STEP() _cs_iter = su_cs_sep_c(&_cs_iter_base, ',', TRU1)
79 /* Is 7-bit enough? */
81 static boole
_has_highbit(char const *s
);
82 static boole
_name_highbit(struct mx_name
*np
);
85 /* fwrite(3) while checking for displayability */
86 static sz
_fwrite_td(struct str
const *input
, enum tdflags flags
,
87 struct str
*outrest
, struct quoteflt
*qf
);
89 /* Convert header fields to RFC 2047 format and write to the file fo */
90 static sz
mime_write_tohdr(struct str
*in
, FILE *fo
,
91 uz
*colp
, enum a_mime_structure_hack msh
);
94 static sz
a_mime__convhdra(struct str
*inp
, FILE *fp
, uz
*colp
,
95 enum a_mime_structure_hack msh
);
97 # define a_mime__convhdra(S,F,C,MSH) mime_write_tohdr(S, F, C, MSH)
100 /* Write an address to a header field */
101 static sz
mime_write_tohdr_a(struct str
*in
, FILE *f
,
102 uz
*colp
, enum a_mime_structure_hack msh
);
104 /* Append to buf, handling resizing */
105 static void _append_str(char **buf
, uz
*size
, uz
*pos
,
106 char const *str
, uz len
);
107 static void _append_conv(char **buf
, uz
*size
, uz
*pos
,
108 char const *str
, uz len
);
112 _has_highbit(char const *s
)
121 while (*s
++ != '\0');
130 _name_highbit(struct mx_name
*np
)
136 if (_has_highbit(np
->n_name
) || _has_highbit(np
->n_fullname
))
145 #endif /* mx_HAVE_ICONV */
147 static sigjmp_buf __mimefwtd_actjmp
; /* TODO someday.. */
148 static int __mimefwtd_sig
; /* TODO someday.. */
149 static n_sighdl_t __mimefwtd_opipe
;
151 __mimefwtd_onsig(int sig
) /* TODO someday, we won't need it no more */
153 NYD
; /* Signal handler */
154 __mimefwtd_sig
= sig
;
155 siglongjmp(__mimefwtd_actjmp
, 1);
159 _fwrite_td(struct str
const *input
, enum tdflags flags
,
160 struct str
*outrest
, struct quoteflt
*qf
)
162 /* TODO note: after send/MIME layer rewrite we will have a string pool
163 * TODO so that memory allocation count drops down massively; for now,
164 * TODO v14.* that is, we pay a lot & heavily depend on the allocator */
165 /* TODO well if we get a broken pipe here, and it happens to
166 * TODO happen pretty easy when sleeping in a full pipe buffer,
167 * TODO then the current codebase performs longjump away;
168 * TODO this leaves memory leaks behind ('think up to 3 per,
169 * TODO dep. upon alloca availability). For this to be fixed
170 * TODO we either need to get rid of the longjmp()s (tm) or
171 * TODO the storage must come from the outside or be tracked
172 * TODO in a carrier struct. Best both. But storage reuse
173 * TODO would be a bigbig win besides */
174 /* *input* _may_ point to non-modifyable buffer; but even then it only
175 * needs to be dup'ed away if we have to transform the content */
186 if ((flags
& TD_ICONV
) && iconvd
!= (iconv_t
)-1) {
192 if (outrest
!= NULL
&& outrest
->l
> 0) {
193 in
.l
= outrest
->l
+ input
->l
;
194 in
.s
= buf
= n_alloc(in
.l
+1);
195 su_mem_copy(in
.s
, outrest
->s
, outrest
->l
);
196 su_mem_copy(&in
.s
[outrest
->l
], input
->s
, input
->l
);
202 /* TODO Sigh, no problem if we have a filter that has a buffer (or
203 * TODO become fed with entire lines, whatever), but for now we need
204 * TODO to ensure we pass entire lines from in here to iconv(3), because
205 * TODO the Citrus iconv(3) will fail tests with stateful encodings
206 * TODO if we do not (only seen on FreeBSD) */
207 #if 0 /* TODO actually not needed indeed, it was known iswprint() error! */
208 if(!(flags
& _TD_EOF
) && outrest
!= NULL
){
212 if((cp
= su_mem_find(in
.s
, '\n', j
= in
.l
)) != NULL
){
215 while(j
> 0 && *cp
== '\n') /* XXX one iteration too much */
218 n_str_assign_buf(outrest
, cp
, j
);
221 n_str_assign(outrest
, &in
);
227 if((err
= n_iconv_str(iconvd
, n_ICONV_UNIDEFAULT
,
228 &out
, &in
, &in
)) != 0){
229 if(err
!= su_ERR_INVAL
)
230 n_iconv_reset(iconvd
);
232 if(outrest
!= NULL
&& in
.l
> 0){
233 /* Incomplete multibyte at EOF is special xxx _INVAL? */
234 if (flags
& _TD_EOF
) {
235 out
.s
= n_realloc(out
.s
, out
.l
+ sizeof(su_utf8_replacer
));
236 if(n_psonce
& n_PSO_UNICODE
){
237 su_mem_copy(&out
.s
[out
.l
], su_utf8_replacer
,
238 sizeof(su_utf8_replacer
) -1);
239 out
.l
+= sizeof(su_utf8_replacer
) -1;
241 out
.s
[out
.l
++] = '?';
243 n_str_add(outrest
, &in
);
250 flags
&= ~_TD_BUFCOPY
;
257 #endif /* mx_HAVE_ICONV */
258 /* Else, if we will modify the data bytes and thus introduce the potential
259 * of messing up multibyte sequences which become split over buffer
260 * boundaries TODO and unless we don't have our filter chain which will
261 * TODO make these hacks go by, buffer data until we see a NL */
262 if((flags
& (TD_ISPR
| TD_DELCTRL
)) && outrest
!= NULL
&&
264 iconvd
== (iconv_t
)-1 &&
266 (!(flags
& _TD_EOF
) || outrest
->l
> 0)
271 for (cp
= &in
.s
[in
.l
]; cp
> in
.s
&& cp
[-1] != '\n'; --cp
)
277 n_str_assign_buf(outrest
, cp
, in
.l
- i
);
279 su_mem_copy(cp
, in
.s
, in
.l
= i
);
280 (in
.s
= cp
)[in
.l
= i
] = '\0';
281 flags
&= ~_TD_BUFCOPY
;
283 n_str_add_buf(outrest
, input
->s
, input
->l
);
291 makeprint(&in
, &out
);
292 else if (flags
& _TD_BUFCOPY
)
293 n_str_dup(&out
, &in
);
296 if (flags
& TD_DELCTRL
)
297 out
.l
= delctrl(out
.s
, out
.l
);
300 __mimefwtd_opipe
= safe_signal(SIGPIPE
, &__mimefwtd_onsig
);
301 if (sigsetjmp(__mimefwtd_actjmp
, 1)) {
306 rv
= quoteflt_push(qf
, out
.s
, out
.l
);
311 if (in
.s
!= input
->s
)
313 safe_signal(SIGPIPE
, __mimefwtd_opipe
);
314 if (__mimefwtd_sig
!= 0)
315 n_raise(__mimefwtd_sig
);
322 mime_write_tohdr(struct str
*in
, FILE *fo
, uz
*colp
,
323 enum a_mime_structure_hack msh
)
325 /* TODO mime_write_tohdr(): we don't know the name of our header->maxcol..
326 * TODO MIME/send layer rewrite: more available state!!
327 * TODO Because of this we cannot make a difference in between structured
328 * TODO and unstructured headers (RFC 2047, 5. (2))
329 * TODO This means, e.g., that this gets called multiple times for a
330 * TODO structured header and always starts thinking it is at column 0.
331 * TODO I.e., it may get called for only the content of a comment etc.,
332 * TODO not knowing anything of its context.
333 * TODO Instead we should have a list of header body content tokens,
334 * TODO convert them, and then dump the converted tokens, breaking lines.
335 * TODO I.e., get rid of convhdra, mime_write_tohdr_a and such...
336 * TODO Somewhen, the following should produce smooth stuff:
337 * TODO ' "Hallo\"," Dr. Backe "Bl\"ö\"d" (Gell) <ha@llöch.en>
338 * TODO "Nochm\"a\"l"<ta@tu.da>(Dümm)'
339 * TODO NOT MULTIBYTE SAFE IF AN ENCODED WORD HAS TO BE SPLIT!
340 * TODO To be better we had to mbtowc_l() (non-std! and no locale!!) and
341 * TODO work char-wise! -> S-CText..
342 * TODO The real problem for STD compatibility is however that "in" is
343 * TODO already iconv(3) encoded to the target character set! We could
344 * TODO also solve it (very expensively!) if we would narrow down to an
345 * TODO encoded word and then iconv(3)+MIME encode in one go, in which
346 * TODO case multibyte errors could be caught! */
348 /* Maximum line length */
349 a_MAXCOL_NENC
= MIME_LINELEN
,
350 a_MAXCOL
= MIME_LINELEN_RFC2047
353 struct str cout
, cin
;
355 _FIRST
= 1<<0, /* Nothing written yet, start of string */
356 _MSH_NOTHING
= 1<<1, /* Now, really: nothing at all has been written */
357 a_ANYENC
= 1<<2, /* We have RFC 2047 anything at least once */
358 _NO_QP
= 1<<3, /* No quoted-printable allowed */
359 _NO_B64
= 1<<4, /* Ditto, base64 */
360 _ENC_LAST
= 1<<5, /* Last round generated encoded word */
361 _SHOULD_BEE
= 1<<6, /* Avoid lines longer than SHOULD via encoding */
363 _RND_MASK
= (1<<_RND_SHIFT
) - 1,
364 _SPACE
= 1<<(_RND_SHIFT
+1), /* Leading whitespace */
365 _8BIT
= 1<<(_RND_SHIFT
+2), /* High bit set */
366 _ENCODE
= 1<<(_RND_SHIFT
+3), /* Need encoding */
367 _ENC_B64
= 1<<(_RND_SHIFT
+4), /* - let it be base64 */
368 _IF_ENC_NO_B64
= 1u<<(_RND_SHIFT
+5), /* - NO! MUST NOT be base64 */
369 _OVERLONG
= 1u<<(_RND_SHIFT
+6) /* Temporarily raised limit */
371 char const *cset7
, *cset8
, *wbot
, *upper
, *wend
, *wcur
;
372 u32 cset7_len
, cset8_len
;
378 cout
.s
= NULL
, cout
.l
= 0;
379 cset7
= ok_vlook(charset_7bit
);
380 cset7_len
= (u32
)su_cs_len(cset7
);
381 cset8
= _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */
382 cset8_len
= (u32
)su_cs_len(cset8
);
385 if(msh
!= a_MIME_SH_NONE
)
386 flags
|= _MSH_NOTHING
;
388 /* RFC 1468, "MIME Considerations":
389 * ISO-2022-JP may also be used in MIME Part 2 headers. The "B"
390 * encoding should be used with ISO-2022-JP text. */
391 /* TODO of course, our current implementation won't deal properly with
392 * TODO any stateful encoding at all... (the standard says each encoded
393 * TODO word must include all necessary reset sequences..., i.e., each
394 * TODO encoded word must be a self-contained iconv(3) life cycle) */
395 if (!su_cs_cmp_case(cset8
, "iso-2022-jp") || mime_enc_target() == MIMEE_B64
)
399 upper
= wbot
+ in
->l
;
402 if(colp
== NULL
|| (col
= *colp
) == 0)
403 col
= sizeof("Mail-Followup-To: ") -1; /* TODO dreadful thing */
405 /* The user may specify empty quoted-strings or comments, keep them! */
407 if(flags
& _MSH_NOTHING
){
408 flags
&= ~_MSH_NOTHING
;
409 putc((msh
== a_MIME_SH_COMMENT
? '(' : '"'), fo
);
413 } else for (; wbot
< upper
; flags
&= ~_FIRST
, wbot
= wend
) {
416 while (wcur
< upper
&& su_cs_is_white(*wcur
)) {
421 /* Any occurrence of whitespace resets prevention of lines >SHOULD via
422 * enforced encoding (xxx SHOULD, but.. encoding is expensive!!) */
424 flags
&= ~_SHOULD_BEE
;
426 /* Data ends with WS - dump it and done.
427 * Also, if we have seen multiple successive whitespace characters, then
428 * if there was no encoded word last, i.e., if we can simply take them
429 * over to the output as-is, keep one WS for possible later separation
430 * purposes and simply print the others as-is, directly! */
435 if ((flags
& (_ENC_LAST
| _SPACE
)) == _SPACE
&& wcur
- wbot
> 1) {
440 /* Skip over a word to next non-whitespace, keep track along the way
441 * whether our 7-bit charset suffices to represent the data */
442 for (wend
= wcur
; wend
< upper
; ++wend
) {
443 if (su_cs_is_white(*wend
))
445 if ((uc
)*wend
& 0x80)
447 /* pure RFC 5322 need to parse these plain */
448 else if(*wend
== '"' || *wend
== '(' || *wend
== ')')
449 flags
|= _IF_ENC_NO_B64
;
452 /* Decide whether the range has to become encoded or not */
453 i
= P2UZ(wend
- wcur
);
454 j
= mime_enc_mustquote(wcur
, i
, MIMEEF_ISHEAD
);
455 /* If it just cannot fit on a SHOULD line length, force encode */
456 if (i
> a_MAXCOL_NENC
) {
457 flags
|= _SHOULD_BEE
; /* (Sigh: SHOULD only, not MUST..) */
460 if ((flags
& _SHOULD_BEE
) || j
> 0) {
463 /* Use base64 if requested or more than 50% -37.5-% of the bytes of
464 * the string need to be encoded */
465 if(flags
& _IF_ENC_NO_B64
)
467 else if ((flags
& _NO_QP
) || j
>= i
>> 1)/*(i >> 2) + (i >> 3))*/
470 su_DBG( if (flags
& _8BIT
) ASSERT(flags
& _ENCODE
); )
472 if (!(flags
& _ENCODE
)) {
473 /* Encoded word produced, but no linear whitespace for necessary RFC
474 * 2047 separation? Generate artificial data (bad standard!) */
475 if ((flags
& (_ENC_LAST
| _SPACE
)) == _ENC_LAST
) {
476 if (col
>= a_MAXCOL
) {
481 if(flags
& _MSH_NOTHING
){
482 flags
&= ~_MSH_NOTHING
;
483 putc((msh
== a_MIME_SH_COMMENT
? '(' : '"'), fo
);
495 /* todo No effort here: (1) v15.0 has to bring complete rewrite,
496 * todo (2) the standard is braindead and (3) usually this is one
497 * todo word only, and why be smarter than the standard? */
499 i
= P2UZ(wend
- wbot
);
500 if (i
+ col
+ ((flags
& _MSH_NOTHING
) != 0) <=
501 (flags
& _OVERLONG
? MIME_LINELEN_MAX
502 : (flags
& a_ANYENC
? a_MAXCOL
: a_MAXCOL_NENC
))) {
503 if(flags
& _MSH_NOTHING
){
504 flags
&= ~_MSH_NOTHING
;
505 putc((msh
== a_MIME_SH_COMMENT
? '(' : '"'), fo
);
509 i
= fwrite(wbot
, sizeof *wbot
, i
, fo
);
515 /* Doesn't fit, try to break the line first; */
518 if (su_cs_is_white(*wbot
)) {
522 putc(' ', fo
); /* Bad standard: artificial data! */
525 if(flags
& _MSH_NOTHING
){
526 flags
&= ~_MSH_NOTHING
;
527 putc((msh
== a_MIME_SH_COMMENT
? '(' : '"'), fo
);
535 /* It is so long that it needs to be broken, effectively causing
536 * artificial spaces to be inserted (bad standard), yuck */
537 /* todo This is not multibyte safe, as above; and completely stupid
538 * todo P.S.: our _SHOULD_BEE prevents these cases in the meanwhile */
539 /* FIXME n_PSO_UNICODE and parse using UTF-8 sync possibility! */
540 wcur
= wbot
+ MIME_LINELEN_MAX
- 8;
545 /* Encoding to encoded word(s); deal with leading whitespace, place
546 * a separator first as necessary: encoded words must always be
547 * separated from text and other encoded words with linear WS.
548 * And if an encoded word was last, intermediate whitespace must
549 * also be encoded, otherwise it would get stripped away! */
550 wcur
= n_UNCONST(n_empty
);
551 if ((flags
& (_ENC_LAST
| _SPACE
)) != _SPACE
) {
552 /* Reinclude whitespace */
554 /* We don't need to place a separator at the very beginning */
555 if (!(flags
& _FIRST
))
556 wcur
= n_UNCONST(" ");
560 flags
|= a_ANYENC
| _ENC_LAST
;
561 n_pstate
|= n_PS_HEADER_NEEDED_MIME
;
564 * An 'encoded-word' may not be more than 75 characters long,
565 * including 'charset', 'encoding', 'encoded-text', and
566 * delimiters. If it is desirable to encode more text than will
567 * fit in an 'encoded-word' of 75 characters, multiple
568 * 'encoded-word's (separated by CRLF SPACE) may be used.
570 * While there is no limit to the length of a multiple-line
571 * header field, each line of a header field that contains one
572 * or more 'encoded-word's is limited to 76 characters */
574 cin
.s
= n_UNCONST(wbot
);
575 cin
.l
= P2UZ(wend
- wbot
);
581 xout
= b64_encode(&cout
, &cin
, B64_ISHEAD
| B64_ISENCWORD
);
583 xout
= qp_encode(&cout
, &cin
, QP_ISHEAD
| QP_ISENCWORD
);
590 /* (Avoid trigraphs in the RFC 2047 placeholder..) */
591 i
= j
+ (flags
& _8BIT
? cset8_len
: cset7_len
) +
592 sizeof("=!!B!!=") -1;
597 /* Unfortunately RFC 2047 explicitly disallows encoded words to be
598 * longer (just like RFC 5322's "a line SHOULD fit in 78 but MAY be
599 * 998 characters long"), so we cannot use the _OVERLONG mechanism,
600 * even though all tested mailers seem to support it */
601 if(i
+ col
<= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ a_MAXCOL
)){
602 if(flags
& _MSH_NOTHING
){
603 flags
&= ~_MSH_NOTHING
;
604 putc((msh
== a_MIME_SH_COMMENT
? '(' : '"'), fo
);
608 fprintf(fo
, "%.1s=?%s?%c?%.*s?=",
609 wcur
, (flags
& _8BIT
? cset8
: cset7
),
610 (flags
& _ENC_B64
? 'B' : 'Q'),
611 (int)cout
.l
, cout
.s
);
617 /* Doesn't fit, try to break the line first */
618 /* TODO I've commented out the _FIRST test since we (1) cannot do
619 * TODO _OVERLONG since (MUAs support but) the standard disallows,
620 * TODO and because of our iconv problem i prefer an empty first line
621 * TODO in favour of a possibly messed up multibytes character. :-( */
622 if (col
> 1 /* TODO && !(flags & _FIRST)*/) {
626 if (!(flags
& _SPACE
)) {
628 wcur
= n_UNCONST(n_empty
);
629 /*flags |= _OVERLONG;*/
630 goto jenc_retry_same
;
633 if (su_cs_is_white(*(wcur
= wbot
)))
637 wcur
= n_UNCONST(n_empty
);
639 /*flags &= ~_OVERLONG;*/
644 /* It is so long that it needs to be broken, effectively causing
645 * artificial data to be inserted (bad standard), yuck */
646 /* todo This is not multibyte safe, as above */
647 /*if (!(flags & _OVERLONG)) { Mechanism explicitly forbidden by 2047
652 /* FIXME n_PSO_UNICODE and parse using UTF-8 sync possibility! */
653 i
= P2UZ(wend
- wbot
) + !!(flags
& _SPACE
);
654 j
= 3 + !(flags
& _ENC_B64
);
658 /* (Note the problem most likely is the transfer-encoding blow,
659 * which is why we test this *after* the decrements.. */
667 if(!(flags
& _MSH_NOTHING
) && msh
!= a_MIME_SH_NONE
){
668 putc((msh
== a_MIME_SH_COMMENT
? ')' : '"'), fo
);
684 a_mime__convhdra(struct str
*inp
, FILE *fp
, uz
*colp
,
685 enum a_mime_structure_hack msh
){
693 if(inp
->l
> 0 && iconvd
!= (iconv_t
)-1){
695 if(n_iconv_str(iconvd
, n_ICONV_NONE
, &ciconv
, inp
, NULL
) != 0){
696 n_iconv_reset(iconvd
);
703 rv
= mime_write_tohdr(inp
, fp
, colp
, msh
);
710 #endif /* mx_HAVE_ICONV */
713 mime_write_tohdr_a(struct str
*in
, FILE *f
, uz
*colp
,
714 enum a_mime_structure_hack msh
)
718 char const *cp
, *lastcp
;
724 if((cp
= routeaddr(lastcp
= in
->s
)) != NULL
&& cp
> lastcp
) {
725 xin
.s
= n_UNCONST(lastcp
);
726 xin
.l
= P2UZ(cp
- lastcp
);
727 if ((size
= a_mime__convhdra(&xin
, f
, colp
, msh
)) < 0)
735 for( ; *cp
!= '\0'; ++cp
){
738 i
= P2UZ(cp
- lastcp
);
740 if(fwrite(lastcp
, 1, i
, f
) != i
)
745 cp
= skip_comment(cp
);
748 /* We want to keep empty comments, too! */
749 xin
.s
= n_UNCONST(lastcp
);
750 xin
.l
= P2UZ(cp
- lastcp
);
751 if ((x
= a_mime__convhdra(&xin
, f
, colp
, a_MIME_SH_COMMENT
)) < 0)
757 i
= P2UZ(cp
- lastcp
);
759 if(fwrite(lastcp
, 1, i
, f
) != i
)
763 for(lastcp
= ++cp
; *cp
!= '\0'; ++cp
){
766 if(*cp
== '\\' && cp
[1] != '\0')
769 /* We want to keep empty quoted-strings, too! */
770 xin
.s
= n_UNCONST(lastcp
);
771 xin
.l
= P2UZ(cp
- lastcp
);
772 if((x
= a_mime__convhdra(&xin
, f
, colp
, a_MIME_SH_QUOTE
)) < 0)
781 i
= P2UZ(cp
- lastcp
);
783 if(fwrite(lastcp
, 1, i
, f
) != i
)
796 _append_str(char **buf
, uz
*size
, uz
*pos
, char const *str
, uz len
)
799 *buf
= n_realloc(*buf
, *size
+= len
);
800 su_mem_copy(&(*buf
)[*pos
], str
, len
);
806 _append_conv(char **buf
, uz
*size
, uz
*pos
, char const *str
, uz len
)
811 in
.s
= n_UNCONST(str
);
813 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
814 _append_str(buf
, size
, pos
, out
.s
, out
.l
);
820 charset_iter_reset(char const *a_charset_to_try_first
) /* TODO elim. dups! */
826 UNUSED(a_charset_to_try_first
);
829 sarr
[2] = ok_vlook(CHARSET_8BIT_OKEY
);
831 if(a_charset_to_try_first
!= NULL
&&
832 su_cs_cmp(a_charset_to_try_first
, sarr
[2]))
833 sarr
[0] = a_charset_to_try_first
;
837 if((sarr
[1] = ok_vlook(sendcharsets
)) == NULL
&&
838 ok_blook(sendcharsets_else_ttycharset
)){
839 cp
= n_UNCONST(ok_vlook(ttycharset
));
840 if(su_cs_cmp(cp
, sarr
[2]) && (sarr
[0] == NULL
|| su_cs_cmp(cp
, sarr
[0])))
844 sarr
[2] = ok_vlook(ttycharset
);
847 sarrl
[2] = len
= su_cs_len(sarr
[2]);
849 if ((cp
= n_UNCONST(sarr
[1])) != NULL
)
850 len
+= (sarrl
[1] = su_cs_len(cp
));
853 if ((cp
= n_UNCONST(sarr
[0])) != NULL
)
854 len
+= (sarrl
[0] = su_cs_len(cp
));
859 _cs_iter_base
= cp
= n_autorec_alloc(len
+ 1 + 1 +1);
862 if ((len
= sarrl
[0]) != 0) {
863 su_mem_copy(cp
, sarr
[0], len
);
867 if ((len
= sarrl
[1]) != 0) {
868 su_mem_copy(cp
, sarr
[1], len
);
874 su_mem_copy(cp
, sarr
[2], len
);
879 return (_cs_iter
!= NULL
);
883 charset_iter_next(void)
889 rv
= (_cs_iter
!= NULL
);
895 charset_iter_is_valid(void)
900 rv
= (_cs_iter
!= NULL
);
917 charset_iter_or_fallback(void)
928 charset_iter_recurse(char *outer_storage
[2]) /* TODO LEGACY FUN, REMOVE */
931 outer_storage
[0] = _cs_iter_base
;
932 outer_storage
[1] = _cs_iter
;
937 charset_iter_restore(char *outer_storage
[2]) /* TODO LEGACY FUN, REMOVE */
940 _cs_iter_base
= outer_storage
[0];
941 _cs_iter
= outer_storage
[1];
947 need_hdrconv(struct header
*hp
) /* TODO once only, then iter */
949 struct n_header_field
*hfp
;
956 struct n_header_field
*chlp
[3]; /* TODO JOINED AFTER COMPOSE! */
959 chlp
[0] = n_poption_arg_C
;
960 chlp
[1] = n_customhdr_list
;
961 chlp
[2] = hp
->h_user_headers
;
963 for(i
= 0; i
< NELEM(chlp
); ++i
)
964 if((hfp
= chlp
[i
]) != NULL
)
965 do if(_has_highbit(hfp
->hf_dat
+ hfp
->hf_nl
+1))
967 while((hfp
= hfp
->hf_next
) != NULL
);
970 if (hp
->h_mft
!= NULL
) {
971 if (_name_highbit(hp
->h_mft
))
974 if (hp
->h_from
!= NULL
) {
975 if (_name_highbit(hp
->h_from
))
977 } else if (_has_highbit(myaddrs(NULL
)))
979 if (hp
->h_reply_to
) {
980 if (_name_highbit(hp
->h_reply_to
))
983 char const *v15compat
;
985 if((v15compat
= ok_vlook(replyto
)) != NULL
)
986 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
987 if(_has_highbit(v15compat
))
989 if(_has_highbit(ok_vlook(reply_to
)))
993 if (_name_highbit(hp
->h_sender
))
995 } else if (_has_highbit(ok_vlook(sender
)))
998 if (_name_highbit(hp
->h_to
))
1000 if (_name_highbit(hp
->h_cc
))
1002 if (_name_highbit(hp
->h_bcc
))
1004 if (_has_highbit(hp
->h_subject
))
1006 rv
= _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
1010 #endif /* mx_HAVE_ICONV */
1013 mime_fromhdr(struct str
const *in
, struct str
*out
, enum tdflags flags
)
1015 /* TODO mime_fromhdr(): is called with strings that contain newlines;
1016 * TODO this is the usual newline problem all around the codebase;
1017 * TODO i.e., if we strip it, then the display misses it ;>
1018 * TODO this is why it is so messy and why S-nail v14.2 plus additional
1019 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
1020 * TODO why our display reflects what is contained in the message: the 1:1
1021 * TODO relationship of message content and display!
1022 * TODO instead a header line should be decoded to what it is (a single
1023 * TODO line that is) and it should be objective to the backend whether
1024 * TODO it'll be folded to fit onto the display or not, e.g., for search
1025 * TODO purposes etc. then the only condition we have to honour in here
1026 * TODO is that whitespace in between multiple adjacent MIME encoded words
1027 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
1028 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
1029 struct str cin
, cout
;
1030 char *p
, *op
, *upper
;
1031 u32 convert
, lastenc
, lastoutl
;
1032 #ifdef mx_HAVE_ICONV
1035 iconv_t fhicd
= (iconv_t
)-1;
1041 *(out
->s
= n_alloc(1)) = '\0';
1046 #ifdef mx_HAVE_ICONV
1047 tcs
= ok_vlook(ttycharset
);
1051 lastenc
= lastoutl
= 0;
1055 if(*p
== '=' && p
[1] == '?'){
1057 #ifdef mx_HAVE_ICONV
1060 while(p
< upper
&& *p
!= '?')
1061 ++p
; /* strip charset */
1067 #ifdef mx_HAVE_ICONV
1068 if(flags
& TD_ICONV
){
1071 if(fhicd
!= R(iconv_t
,-1)){
1072 n_iconv_close(fhicd
);
1073 fhicd
= R(iconv_t
,-1);
1076 i
= P2UZ(p
- cbeg
) - 1;
1081 cs
= n_lofi_alloc(i
+1);
1083 su_mem_copy(cs
, cbeg
, i
);
1086 /* RFC 2231 extends the RFC 2047 character set definition in
1087 * encoded words by language tags - silently strip those off */
1088 if((ltag
= su_cs_find_c(cs
, '*')) != NIL
)
1091 fhicd
= su_cs_cmp_case(cs
, tcs
)
1092 ? n_iconv_open(tcs
, cs
) : R(iconv_t
,-1);
1101 convert
= CONV_FROMB64
;
1104 convert
= CONV_FROMQP
;
1106 default: /* invalid, ignore */
1115 if (PCMP(p
+ 1, >=, upper
))
1117 if (*p
++ == '?' && *p
== '=')
1122 /* Shortcut on empty POI; _ensure_ buffer */
1124 out
= n_str_add_buf(out
, n_qm
, 1);
1125 lastenc
= lastoutl
= --out
->l
;
1131 if (convert
== CONV_FROMB64
) {
1132 if(!b64_decode_header(&cout
, &cin
))
1133 n_str_assign_cp(&cout
, _("[Invalid Base64 encoding]"));
1134 }else if(!qp_decode_header(&cout
, &cin
))
1135 n_str_assign_cp(&cout
, _("[Invalid Quoted-Printable encoding]"));
1136 /* Normalize all decoded newlines to spaces XXX only \0/\n yet */
1142 for(any
= FAL0
, i
= cout
.l
; i
-- != 0;)
1154 /* I18N: must be non-empty, last must be closing bracket/xy */
1155 xcp
= _("[Content normalized: ]");
1158 n_str_add_buf(&cout
, xcp
, i
);
1159 su_mem_move(&cout
.s
[i
- 1], cout
.s
, j
);
1160 su_mem_copy(&cout
.s
[0], xcp
, i
- 1);
1161 cout
.s
[cout
.l
- 1] = xcp
[i
- 1];
1167 #ifdef mx_HAVE_ICONV
1168 /* TODO Does not really work if we have assigned some ASCII or even
1169 * TODO translated strings because of errors! */
1170 if ((flags
& TD_ICONV
) && fhicd
!= (iconv_t
)-1) {
1171 cin
.s
= NULL
, cin
.l
= 0; /* XXX string pool ! */
1172 convert
= n_iconv_str(fhicd
, n_ICONV_UNIDEFAULT
, &cin
, &cout
, NIL
);
1173 out
= n_str_add(out
, &cin
);
1174 if (convert
) {/* su_ERR_INVAL at EOS */
1175 n_iconv_reset(fhicd
);
1176 out
= n_str_add_buf(out
, n_qm
, 1);/* TODO unicode replacement */
1181 out
= n_str_add(out
, &cout
);
1182 lastenc
= lastoutl
= out
->l
;
1189 onlyws
= (lastenc
> 0);
1193 if (op
[0] == '=' && (PCMP(op
+ 1, ==, upper
) || op
[1] == '?'))
1195 if (onlyws
&& !su_cs_is_blank(*op
))
1199 out
= n_str_add_buf(out
, p
, P2UZ(op
- p
));
1201 if (!onlyws
|| lastoutl
!= lastenc
)
1207 ASSERT(out
->s
!= NIL
);
1208 out
->s
[out
->l
] = '\0';
1210 if (flags
& TD_ISPR
) {
1211 makeprint(out
, &cout
);
1215 if (flags
& TD_DELCTRL
)
1216 out
->l
= delctrl(out
->s
, out
->l
);
1217 #ifdef mx_HAVE_ICONV
1218 if (fhicd
!= (iconv_t
)-1)
1219 n_iconv_close(fhicd
);
1227 mime_fromaddr(char const *name
)
1229 char const *cp
, *lastcp
;
1231 uz ressz
= 1, rescur
= 0;
1236 if (*name
== '\0') {
1237 res
= savestr(name
);
1241 if ((cp
= routeaddr(name
)) != NULL
&& cp
> name
) {
1242 _append_conv(&res
, &ressz
, &rescur
, name
, P2UZ(cp
- name
));
1247 for ( ; *cp
; ++cp
) {
1250 _append_str(&res
, &ressz
, &rescur
, lastcp
, P2UZ(cp
- lastcp
+ 1));
1252 cp
= skip_comment(cp
);
1254 _append_conv(&res
, &ressz
, &rescur
, lastcp
, P2UZ(cp
- lastcp
));
1261 if (*cp
== '\\' && cp
[1] != '\0')
1268 _append_str(&res
, &ressz
, &rescur
, lastcp
, P2UZ(cp
- lastcp
));
1273 res
= savestrbuf(res
, rescur
);
1283 xmime_write(char const *ptr
, uz size
, FILE *f
, enum conversion convert
,
1284 enum tdflags dflags
, struct str
* volatile outrest
,
1285 struct str
* volatile inrest
)
1288 struct quoteflt
*qf
;
1291 quoteflt_reset(qf
= quoteflt_dummy(), f
);
1292 rv
= mime_write(ptr
, size
, f
, convert
, dflags
, qf
, outrest
, inrest
);
1298 static sigjmp_buf __mimemw_actjmp
; /* TODO someday.. */
1299 static int __mimemw_sig
; /* TODO someday.. */
1300 static n_sighdl_t __mimemw_opipe
;
1302 __mimemw_onsig(int sig
) /* TODO someday, we won't need it no more */
1304 NYD
; /* Signal handler */
1306 siglongjmp(__mimemw_actjmp
, 1);
1310 mime_write(char const *ptr
, uz size
, FILE *f
,
1311 enum conversion convert
, enum tdflags
volatile dflags
,
1312 struct quoteflt
*qf
, struct str
* volatile outrest
,
1313 struct str
* volatile inrest
)
1315 /* TODO note: after send/MIME layer rewrite we will have a string pool
1316 * TODO so that memory allocation count drops down massively; for now,
1317 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator.
1318 * TODO P.S.: furthermore all this encapsulated in filter objects instead */
1323 dflags
|= _TD_BUFCOPY
;
1324 in
.s
= n_UNCONST(ptr
);
1329 if((xsize
= size
) == 0){
1330 if(inrest
!= NULL
&& inrest
->l
!= 0)
1332 if(outrest
!= NULL
&& outrest
->l
!= 0)
1337 /* TODO This crap requires linewise input, then. We need a filter chain
1338 * TODO as in input->iconv->base64 where each filter can have its own
1339 * TODO buffer, with a filter->fflush() call to get rid of those! */
1340 #ifdef mx_HAVE_ICONV
1341 if ((dflags
& TD_ICONV
) && iconvd
!= (iconv_t
)-1 &&
1342 (convert
== CONV_TOQP
|| convert
== CONV_8BIT
||
1343 convert
== CONV_TOB64
|| convert
== CONV_TOHDR
)) {
1344 if (n_iconv_str(iconvd
, n_ICONV_NONE
, &out
, &in
, NULL
) != 0) {
1345 n_iconv_reset(iconvd
);
1346 /* TODO This causes hard-failure. We would need to have an action
1347 * TODO policy FAIL|IGNORE|SETERROR(but continue) */
1353 dflags
&= ~_TD_BUFCOPY
;
1358 if(inrest
!= NULL
&& inrest
->l
> 0){
1364 out
.s
= n_alloc(in
.l
+ inrest
->l
+ 1);
1365 su_mem_copy(out
.s
, inrest
->s
, inrest
->l
);
1367 su_mem_copy(&out
.s
[inrest
->l
], in
.s
, in
.l
);
1370 (in
.s
= out
.s
)[in
.l
+= inrest
->l
] = '\0';
1374 dflags
&= ~_TD_BUFCOPY
;
1379 __mimemw_opipe
= safe_signal(SIGPIPE
, &__mimemw_onsig
);
1380 if (sigsetjmp(__mimemw_actjmp
, 1))
1385 if(!qp_decode_part(&out
, &in
, outrest
, inrest
)){
1386 n_err(_("Invalid Quoted-Printable encoding ignored\n"));
1387 xsize
= 0; /* TODO size = -1 stops outer levels! */
1392 if(qp_encode(&out
, &in
, QP_NONE
) == NULL
){
1393 xsize
= 0; /* TODO size = -1 stops outer levels! */
1398 xsize
= quoteflt_push(qf
, in
.s
, in
.l
);
1401 if(!b64_decode_part(&out
, &in
, outrest
, inrest
))
1406 case CONV_FROMB64_T
:
1407 if(!b64_decode_part(&out
, &in
, outrest
, inrest
)){
1409 n_err(_("Invalid Base64 encoding ignored\n"));
1410 xsize
= 0; /* TODO size = -1 stops outer levels! */
1415 if ((xsize
= out
.l
) != 0)
1416 xsize
= _fwrite_td(&out
, (dflags
& ~_TD_BUFCOPY
), outrest
, qf
);
1419 /* TODO hack which is necessary unless this is a filter based approach
1420 * TODO and each filter has its own buffer (as necessary): we must not
1421 * TODO pass through a number of bytes which causes padding, otherwise we
1422 * TODO produce multiple adjacent base64 streams, and that is not treated
1423 * TODO in the same relaxed fashion like completely bogus bytes by at
1424 * TODO least mutt and OpenSSL. So we need an expensive workaround
1425 * TODO unless we have input->iconv->base64 filter chain as such!! :( */
1426 if(size
!= 0 && /* for Coverity, else ASSERT() */ inrest
!= NULL
){
1427 if(in
.l
> B64_ENCODE_INPUT_PER_LINE
){
1430 i
= in
.l
% B64_ENCODE_INPUT_PER_LINE
;
1434 ASSERT(inrest
->l
== 0);
1435 inrest
->s
= n_realloc(inrest
->s
, i
+1);
1436 su_mem_copy(inrest
->s
, &in
.s
[in
.l
], i
);
1437 inrest
->s
[inrest
->l
= i
] = '\0';
1439 }else if(in
.l
< B64_ENCODE_INPUT_PER_LINE
){
1440 inrest
->s
= n_realloc(inrest
->s
, in
.l
+1);
1441 su_mem_copy(inrest
->s
, in
.s
, in
.l
);
1442 inrest
->s
[inrest
->l
= in
.l
] = '\0';
1448 if(b64_encode(&out
, &in
, B64_LF
| B64_MULTILINE
) == NULL
){
1453 xsize
= fwrite(out
.s
, sizeof *out
.s
, out
.l
, f
);
1454 if (xsize
!= (sz
)out
.l
)
1458 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
| (dflags
& TD_DELCTRL
));
1459 xsize
= quoteflt_push(qf
, out
.s
, out
.l
);
1462 xsize
= mime_write_tohdr(&in
, f
, NULL
, a_MIME_SH_NONE
);
1467 if(dflags
& _TD_BUFCOPY
){
1468 n_str_dup(&out
, &in
);
1471 dflags
&= ~_TD_BUFCOPY
;
1474 xsize
= mime_write_tohdr_a(&in
, f
, &col
, a_MIME_SH_NONE
);
1477 xsize
= _fwrite_td(&in
, dflags
, NULL
, qf
);
1486 safe_signal(SIGPIPE
, __mimemw_opipe
);
1487 if (__mimemw_sig
!= 0)
1488 n_raise(__mimemw_sig
);
1493 #include "su/code-ou.h"