"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.19/src/mx/mime.c" (26 Apr 2020, 44594 Bytes) of package /linux/misc/s-nail-14.9.19.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "mime.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.18_vs_14.9.19.

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ MIME support functions.
    3  *@ TODO Complete rewrite.
    4  *
    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
    8  */
    9 /*
   10  * Copyright (c) 2000
   11  * Gunnar Ritter.  All rights reserved.
   12  *
   13  * Redistribution and use in source and binary forms, with or without
   14  * modification, are permitted provided that the following conditions
   15  * are met:
   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.
   28  *
   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
   39  * SUCH DAMAGE.
   40  */
   41 #undef su_FILE
   42 #define su_FILE mime
   43 #define mx_SOURCE
   44 
   45 #ifndef mx_HAVE_AMALGAMATION
   46 # include "mx/nail.h"
   47 #endif
   48 
   49 #include <su/cs.h>
   50 #include <su/mem.h>
   51 #include <su/utf.h>
   52 
   53 /* TODO nonsense (should be filter chain!) */
   54 #include "mx/filter-quote.h"
   55 #include "mx/iconv.h"
   56 #include "mx/names.h"
   57 #include "mx/sigs.h"
   58 #include "mx/ui-str.h"
   59 
   60 /* TODO fake */
   61 #include "su/code-in.h"
   62 
   63 /* Don't ask, but it keeps body and soul together */
   64 enum a_mime_structure_hack{
   65    a_MIME_SH_NONE,
   66    a_MIME_SH_COMMENT,
   67    a_MIME_SH_QUOTE
   68 };
   69 
   70 static char                   *_cs_iter_base, *_cs_iter;
   71 #ifdef mx_HAVE_ICONV
   72 # define _CS_ITER_GET() \
   73    ((_cs_iter != NULL) ? _cs_iter : ok_vlook(CHARSET_8BIT_OKEY))
   74 #else
   75 # define _CS_ITER_GET() ((_cs_iter != NULL) ? _cs_iter : ok_vlook(ttycharset))
   76 #endif
   77 #define _CS_ITER_STEP() _cs_iter = su_cs_sep_c(&_cs_iter_base, ',', TRU1)
   78 
   79 /* Is 7-bit enough? */
   80 #ifdef mx_HAVE_ICONV
   81 static boole           _has_highbit(char const *s);
   82 static boole           _name_highbit(struct mx_name *np);
   83 #endif
   84 
   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);
   88 
   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);
   92 
   93 #ifdef mx_HAVE_ICONV
   94 static sz a_mime__convhdra(struct str *inp, FILE *fp, uz *colp,
   95                   enum a_mime_structure_hack msh);
   96 #else
   97 # define a_mime__convhdra(S,F,C,MSH) mime_write_tohdr(S, F, C, MSH)
   98 #endif
   99 
  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);
  103 
  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);
  109 
  110 #ifdef mx_HAVE_ICONV
  111 static boole
  112 _has_highbit(char const *s)
  113 {
  114    boole rv = TRU1;
  115    NYD_IN;
  116 
  117    if (s) {
  118       do
  119          if ((u8)*s & 0x80)
  120             goto jleave;
  121       while (*s++ != '\0');
  122    }
  123    rv = FAL0;
  124 jleave:
  125    NYD_OU;
  126    return rv;
  127 }
  128 
  129 static boole
  130 _name_highbit(struct mx_name *np)
  131 {
  132    boole rv = TRU1;
  133    NYD_IN;
  134 
  135    while (np) {
  136       if (_has_highbit(np->n_name) || _has_highbit(np->n_fullname))
  137          goto jleave;
  138       np = np->n_flink;
  139    }
  140    rv = FAL0;
  141 jleave:
  142    NYD_OU;
  143    return rv;
  144 }
  145 #endif /* mx_HAVE_ICONV */
  146 
  147 static sigjmp_buf       __mimefwtd_actjmp; /* TODO someday.. */
  148 static int              __mimefwtd_sig; /* TODO someday.. */
  149 static n_sighdl_t  __mimefwtd_opipe;
  150 static void
  151 __mimefwtd_onsig(int sig) /* TODO someday, we won't need it no more */
  152 {
  153    NYD; /* Signal handler */
  154    __mimefwtd_sig = sig;
  155    siglongjmp(__mimefwtd_actjmp, 1);
  156 }
  157 
  158 static sz
  159 _fwrite_td(struct str const *input, enum tdflags flags,
  160    struct str *outrest, struct quoteflt *qf)
  161 {
  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 */
  176    struct str in, out;
  177    sz rv;
  178    NYD_IN;
  179    UNUSED(outrest);
  180 
  181    in = *input;
  182    out.s = NULL;
  183    out.l = 0;
  184 
  185 #ifdef mx_HAVE_ICONV
  186    if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
  187       int err;
  188       char *buf;
  189 
  190       buf = NULL;
  191 
  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);
  197          outrest->l = 0;
  198       }
  199 
  200       rv = 0;
  201 
  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){
  209          uz i, j;
  210          char const *cp;
  211 
  212          if((cp = su_mem_find(in.s, '\n', j = in.l)) != NULL){
  213             i = P2UZ(cp - in.s);
  214             j -= i;
  215             while(j > 0 && *cp == '\n') /* XXX one iteration too much */
  216                ++cp, --j, ++i;
  217             if(j != 0)
  218                n_str_assign_buf(outrest, cp, j);
  219             in.l = i;
  220          }else{
  221             n_str_assign(outrest, &in);
  222             goto jleave;
  223          }
  224       }
  225 #endif
  226 
  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);
  231 
  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;
  240                }else
  241                   out.s[out.l++] = '?';
  242             } else
  243                n_str_add(outrest, &in);
  244          }else
  245             rv = -1;
  246       }
  247       in = out;
  248       out.l = 0;
  249       out.s = NULL;
  250       flags &= ~_TD_BUFCOPY;
  251 
  252       if(buf != NULL)
  253          n_free(buf);
  254       if(rv < 0)
  255          goto jleave;
  256    }else
  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 &&
  263 #ifdef mx_HAVE_ICONV
  264          iconvd == (iconv_t)-1 &&
  265 #endif
  266          (!(flags & _TD_EOF) || outrest->l > 0)
  267    ) {
  268       uz i;
  269       char *cp;
  270 
  271       for (cp = &in.s[in.l]; cp > in.s && cp[-1] != '\n'; --cp)
  272          ;
  273       i = P2UZ(cp - in.s);
  274 
  275       if (i != in.l) {
  276          if (i > 0) {
  277             n_str_assign_buf(outrest, cp, in.l - i);
  278             cp = n_alloc(i +1);
  279             su_mem_copy(cp, in.s, in.l = i);
  280             (in.s = cp)[in.l = i] = '\0';
  281             flags &= ~_TD_BUFCOPY;
  282          } else {
  283             n_str_add_buf(outrest, input->s, input->l);
  284             rv = 0;
  285             goto jleave;
  286          }
  287       }
  288    }
  289 
  290    if (flags & TD_ISPR)
  291       makeprint(&in, &out);
  292    else if (flags & _TD_BUFCOPY)
  293       n_str_dup(&out, &in);
  294    else
  295       out = in;
  296    if (flags & TD_DELCTRL)
  297       out.l = delctrl(out.s, out.l);
  298 
  299    __mimefwtd_sig = 0;
  300    __mimefwtd_opipe = safe_signal(SIGPIPE, &__mimefwtd_onsig);
  301    if (sigsetjmp(__mimefwtd_actjmp, 1)) {
  302       rv = 0;
  303       goto j__sig;
  304    }
  305 
  306    rv = quoteflt_push(qf, out.s, out.l);
  307 
  308 j__sig:
  309    if (out.s != in.s)
  310       n_free(out.s);
  311    if (in.s != input->s)
  312       n_free(in.s);
  313    safe_signal(SIGPIPE, __mimefwtd_opipe);
  314    if (__mimefwtd_sig != 0)
  315       n_raise(__mimefwtd_sig);
  316 jleave:
  317    NYD_OU;
  318    return rv;
  319 }
  320 
  321 static sz
  322 mime_write_tohdr(struct str *in, FILE *fo, uz *colp,
  323    enum a_mime_structure_hack msh)
  324 {
  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! */
  347    enum {
  348       /* Maximum line length */
  349       a_MAXCOL_NENC = MIME_LINELEN,
  350       a_MAXCOL = MIME_LINELEN_RFC2047
  351    };
  352 
  353    struct str cout, cin;
  354    enum {
  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 */
  362       _RND_SHIFT  = 7,
  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       _OVERLONG   = 1<<(_RND_SHIFT+5)     /* Temporarily raised limit */
  369    } flags;
  370    char const *cset7, *cset8, *wbot, *upper, *wend, *wcur;
  371    u32 cset7_len, cset8_len;
  372    uz col, i, j;
  373    sz size;
  374 
  375    NYD_IN;
  376 
  377    cout.s = NULL, cout.l = 0;
  378    cset7 = ok_vlook(charset_7bit);
  379    cset7_len = (u32)su_cs_len(cset7);
  380    cset8 = _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */
  381    cset8_len = (u32)su_cs_len(cset8);
  382 
  383    flags = _FIRST;
  384    if(msh != a_MIME_SH_NONE)
  385       flags |= _MSH_NOTHING;
  386 
  387    /* RFC 1468, "MIME Considerations":
  388     *     ISO-2022-JP may also be used in MIME Part 2 headers.  The "B"
  389     *     encoding should be used with ISO-2022-JP text. */
  390    /* TODO of course, our current implementation won't deal properly with
  391     * TODO any stateful encoding at all... (the standard says each encoded
  392     * TODO word must include all necessary reset sequences..., i.e., each
  393     * TODO encoded word must be a self-contained iconv(3) life cycle) */
  394    if (!su_cs_cmp_case(cset8, "iso-2022-jp") || mime_enc_target() == MIMEE_B64)
  395       flags |= _NO_QP;
  396 
  397    wbot = in->s;
  398    upper = wbot + in->l;
  399    size = 0;
  400 
  401    if(colp == NULL || (col = *colp) == 0)
  402       col = sizeof("Mail-Followup-To: ") -1; /* TODO dreadful thing */
  403 
  404    /* The user may specify empty quoted-strings or comments, keep them! */
  405    if(wbot == upper) {
  406       if(flags & _MSH_NOTHING){
  407          flags &= ~_MSH_NOTHING;
  408          putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
  409          size = 1;
  410          ++col;
  411       }
  412    } else for (; wbot < upper; flags &= ~_FIRST, wbot = wend) {
  413       flags &= _RND_MASK;
  414       wcur = wbot;
  415       while (wcur < upper && su_cs_is_white(*wcur)) {
  416          flags |= _SPACE;
  417          ++wcur;
  418       }
  419 
  420       /* Any occurrence of whitespace resets prevention of lines >SHOULD via
  421        * enforced encoding (xxx SHOULD, but.. encoding is expensive!!) */
  422       if (flags & _SPACE)
  423          flags &= ~_SHOULD_BEE;
  424 
  425      /* Data ends with WS - dump it and done.
  426       * Also, if we have seen multiple successive whitespace characters, then
  427       * if there was no encoded word last, i.e., if we can simply take them
  428       * over to the output as-is, keep one WS for possible later separation
  429       * purposes and simply print the others as-is, directly! */
  430       if (wcur == upper) {
  431          wend = wcur;
  432          goto jnoenc_putws;
  433       }
  434       if ((flags & (_ENC_LAST | _SPACE)) == _SPACE && wcur - wbot > 1) {
  435          wend = wcur - 1;
  436          goto jnoenc_putws;
  437       }
  438 
  439       /* Skip over a word to next non-whitespace, keep track along the way
  440        * whether our 7-bit charset suffices to represent the data */
  441       for (wend = wcur; wend < upper; ++wend) {
  442          if (su_cs_is_white(*wend))
  443             break;
  444          if ((uc)*wend & 0x80)
  445             flags |= _8BIT;
  446       }
  447 
  448       /* Decide whether the range has to become encoded or not */
  449       i = P2UZ(wend - wcur);
  450       j = mime_enc_mustquote(wcur, i, MIMEEF_ISHEAD);
  451       /* If it just cannot fit on a SHOULD line length, force encode */
  452       if (i > a_MAXCOL_NENC) {
  453          flags |= _SHOULD_BEE; /* (Sigh: SHOULD only, not MUST..) */
  454          goto j_beejump;
  455       }
  456       if ((flags & _SHOULD_BEE) || j > 0) {
  457 j_beejump:
  458          flags |= _ENCODE;
  459          /* Use base64 if requested or more than 50% -37.5-% of the bytes of
  460           * the string need to be encoded */
  461          if ((flags & _NO_QP) || j >= i >> 1)/*(i >> 2) + (i >> 3))*/
  462             flags |= _ENC_B64;
  463       }
  464       su_DBG( if (flags & _8BIT) ASSERT(flags & _ENCODE); )
  465 
  466       if (!(flags & _ENCODE)) {
  467          /* Encoded word produced, but no linear whitespace for necessary RFC
  468           * 2047 separation?  Generate artificial data (bad standard!) */
  469          if ((flags & (_ENC_LAST | _SPACE)) == _ENC_LAST) {
  470             if (col >= a_MAXCOL) {
  471                putc('\n', fo);
  472                ++size;
  473                col = 0;
  474             }
  475             if(flags & _MSH_NOTHING){
  476                flags &= ~_MSH_NOTHING;
  477                putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
  478                ++size;
  479                ++col;
  480             }
  481             putc(' ', fo);
  482             ++size;
  483             ++col;
  484          }
  485 
  486 jnoenc_putws:
  487          flags &= ~_ENC_LAST;
  488 
  489          /* todo No effort here: (1) v15.0 has to bring complete rewrite,
  490           * todo (2) the standard is braindead and (3) usually this is one
  491           * todo word only, and why be smarter than the standard? */
  492 jnoenc_retry:
  493          i = P2UZ(wend - wbot);
  494          if (i + col + ((flags & _MSH_NOTHING) != 0) <=
  495                   (flags & _OVERLONG ? MIME_LINELEN_MAX
  496                    : (flags & a_ANYENC ? a_MAXCOL : a_MAXCOL_NENC))) {
  497             if(flags & _MSH_NOTHING){
  498                flags &= ~_MSH_NOTHING;
  499                putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
  500                ++size;
  501                ++col;
  502             }
  503             i = fwrite(wbot, sizeof *wbot, i, fo);
  504             size += i;
  505             col += i;
  506             continue;
  507          }
  508 
  509          /* Doesn't fit, try to break the line first; */
  510          if (col > 1) {
  511             putc('\n', fo);
  512             if (su_cs_is_white(*wbot)) {
  513                putc((uc)*wbot, fo);
  514                ++wbot;
  515             } else
  516                putc(' ', fo); /* Bad standard: artificial data! */
  517             size += 2;
  518             col = 1;
  519             if(flags & _MSH_NOTHING){
  520                flags &= ~_MSH_NOTHING;
  521                putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
  522                ++size;
  523                ++col;
  524             }
  525             flags |= _OVERLONG;
  526             goto jnoenc_retry;
  527          }
  528 
  529          /* It is so long that it needs to be broken, effectively causing
  530           * artificial spaces to be inserted (bad standard), yuck */
  531          /* todo This is not multibyte safe, as above; and completely stupid
  532           * todo P.S.: our _SHOULD_BEE prevents these cases in the meanwhile */
  533 /* FIXME n_PSO_UNICODE and parse using UTF-8 sync possibility! */
  534          wcur = wbot + MIME_LINELEN_MAX - 8;
  535          while (wend > wcur)
  536             wend -= 4;
  537          goto jnoenc_retry;
  538       } else {
  539          /* Encoding to encoded word(s); deal with leading whitespace, place
  540           * a separator first as necessary: encoded words must always be
  541           * separated from text and other encoded words with linear WS.
  542           * And if an encoded word was last, intermediate whitespace must
  543           * also be encoded, otherwise it would get stripped away! */
  544          wcur = n_UNCONST(n_empty);
  545          if ((flags & (_ENC_LAST | _SPACE)) != _SPACE) {
  546             /* Reinclude whitespace */
  547             flags &= ~_SPACE;
  548             /* We don't need to place a separator at the very beginning */
  549             if (!(flags & _FIRST))
  550                wcur = n_UNCONST(" ");
  551          } else
  552             wcur = wbot++;
  553 
  554          flags |= a_ANYENC | _ENC_LAST;
  555          n_pstate |= n_PS_HEADER_NEEDED_MIME;
  556 
  557          /* RFC 2047:
  558           *    An 'encoded-word' may not be more than 75 characters long,
  559           *    including 'charset', 'encoding', 'encoded-text', and
  560           *    delimiters.  If it is desirable to encode more text than will
  561           *    fit in an 'encoded-word' of 75 characters, multiple
  562           *    'encoded-word's (separated by CRLF SPACE) may be used.
  563           *
  564           *    While there is no limit to the length of a multiple-line
  565           *    header field, each line of a header field that contains one
  566           *    or more 'encoded-word's is limited to 76 characters */
  567 jenc_retry:
  568          cin.s = n_UNCONST(wbot);
  569          cin.l = P2UZ(wend - wbot);
  570 
  571          /* C99 */{
  572             struct str *xout;
  573 
  574             if(flags & _ENC_B64)
  575                xout = b64_encode(&cout, &cin, B64_ISHEAD | B64_ISENCWORD);
  576             else
  577                xout = qp_encode(&cout, &cin, QP_ISHEAD | QP_ISENCWORD);
  578             if(xout == NULL){
  579                size = -1;
  580                break;
  581             }
  582             j = xout->l;
  583          }
  584          /* (Avoid trigraphs in the RFC 2047 placeholder..) */
  585          i = j + (flags & _8BIT ? cset8_len : cset7_len) + sizeof("=!!B!!=") -1;
  586          if (*wcur != '\0')
  587             ++i;
  588 
  589 jenc_retry_same:
  590          /* Unfortunately RFC 2047 explicitly disallows encoded words to be
  591           * longer (just like RFC 5322's "a line SHOULD fit in 78 but MAY be
  592           * 998 characters long"), so we cannot use the _OVERLONG mechanism,
  593           * even though all tested mailers seem to support it */
  594          if (i + col <= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ a_MAXCOL)) {
  595             if(flags & _MSH_NOTHING){
  596                flags &= ~_MSH_NOTHING;
  597                putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
  598                ++size;
  599                ++col;
  600             }
  601             fprintf(fo, "%.1s=?%s?%c?%.*s?=",
  602                wcur, (flags & _8BIT ? cset8 : cset7),
  603                (flags & _ENC_B64 ? 'B' : 'Q'),
  604                (int)cout.l, cout.s);
  605             size += i;
  606             col += i;
  607             continue;
  608          }
  609 
  610          /* Doesn't fit, try to break the line first */
  611          /* TODO I've commented out the _FIRST test since we (1) cannot do
  612           * TODO _OVERLONG since (MUAs support but) the standard disallows,
  613           * TODO and because of our iconv problem i prefer an empty first line
  614           * TODO in favour of a possibly messed up multibytes character. :-( */
  615          if (col > 1 /* TODO && !(flags & _FIRST)*/) {
  616             putc('\n', fo);
  617             size += 2;
  618             col = 1;
  619             if (!(flags & _SPACE)) {
  620                putc(' ', fo);
  621                wcur = n_UNCONST(n_empty);
  622                /*flags |= _OVERLONG;*/
  623                goto jenc_retry_same;
  624             } else {
  625                putc((uc)*wcur, fo);
  626                if (su_cs_is_white(*(wcur = wbot)))
  627                   ++wbot;
  628                else {
  629                   flags &= ~_SPACE;
  630                   wcur = n_UNCONST(n_empty);
  631                }
  632                /*flags &= ~_OVERLONG;*/
  633                goto jenc_retry;
  634             }
  635          }
  636 
  637          /* It is so long that it needs to be broken, effectively causing
  638           * artificial data to be inserted (bad standard), yuck */
  639          /* todo This is not multibyte safe, as above */
  640          /*if (!(flags & _OVERLONG)) { Mechanism explicitly forbidden by 2047
  641             flags |= _OVERLONG;
  642             goto jenc_retry;
  643          }*/
  644 
  645 /* FIXME n_PSO_UNICODE and parse using UTF-8 sync possibility! */
  646          i = P2UZ(wend - wbot) + !!(flags & _SPACE);
  647          j = 3 + !(flags & _ENC_B64);
  648          for (;;) {
  649             wend -= j;
  650             i -= j;
  651             /* (Note the problem most likely is the transfer-encoding blow,
  652              * which is why we test this *after* the decrements.. */
  653             if (i <= a_MAXCOL)
  654                break;
  655          }
  656          goto jenc_retry;
  657       }
  658    }
  659 
  660    if(!(flags & _MSH_NOTHING) && msh != a_MIME_SH_NONE){
  661       putc((msh == a_MIME_SH_COMMENT ? ')' : '"'), fo);
  662       ++size;
  663       ++col;
  664    }
  665 
  666    if(cout.s != NULL)
  667       n_free(cout.s);
  668 
  669    if(colp != NULL)
  670       *colp = col;
  671    NYD_OU;
  672    return size;
  673 }
  674 
  675 #ifdef mx_HAVE_ICONV
  676 static sz
  677 a_mime__convhdra(struct str *inp, FILE *fp, uz *colp,
  678       enum a_mime_structure_hack msh){
  679    struct str ciconv;
  680    sz rv;
  681    NYD_IN;
  682 
  683    rv = 0;
  684    ciconv.s = NULL;
  685 
  686    if(inp->l > 0 && iconvd != (iconv_t)-1){
  687       ciconv.l = 0;
  688       if(n_iconv_str(iconvd, n_ICONV_NONE, &ciconv, inp, NULL) != 0){
  689          n_iconv_reset(iconvd);
  690          goto jleave;
  691       }
  692       *inp = ciconv;
  693    }
  694 
  695    rv = mime_write_tohdr(inp, fp, colp, msh);
  696 jleave:
  697    if(ciconv.s != NULL)
  698       n_free(ciconv.s);
  699    NYD_OU;
  700    return rv;
  701 }
  702 #endif /* mx_HAVE_ICONV */
  703 
  704 static sz
  705 mime_write_tohdr_a(struct str *in, FILE *f, uz *colp,
  706    enum a_mime_structure_hack msh)
  707 {
  708    struct str xin;
  709    uz i;
  710    char const *cp, *lastcp;
  711    sz size, x;
  712    NYD_IN;
  713 
  714    in->s[in->l] = '\0';
  715 
  716    if((cp = routeaddr(lastcp = in->s)) != NULL && cp > lastcp) {
  717       xin.s = n_UNCONST(lastcp);
  718       xin.l = P2UZ(cp - lastcp);
  719       if ((size = a_mime__convhdra(&xin, f, colp, msh)) < 0)
  720          goto jleave;
  721       lastcp = cp;
  722    } else {
  723       cp = lastcp;
  724       size = 0;
  725    }
  726 
  727    for( ; *cp != '\0'; ++cp){
  728       switch(*cp){
  729       case '(':
  730          i = P2UZ(cp - lastcp);
  731          if(i > 0){
  732             if(fwrite(lastcp, 1, i, f) != i)
  733                goto jerr;
  734             size += i;
  735          }
  736          lastcp = ++cp;
  737          cp = skip_comment(cp);
  738          if(cp > lastcp)
  739             --cp;
  740          /* We want to keep empty comments, too! */
  741          xin.s = n_UNCONST(lastcp);
  742          xin.l = P2UZ(cp - lastcp);
  743          if ((x = a_mime__convhdra(&xin, f, colp, a_MIME_SH_COMMENT)) < 0)
  744             goto jerr;
  745          size += x;
  746          lastcp = &cp[1];
  747          break;
  748       case '"':
  749          i = P2UZ(cp - lastcp);
  750          if(i > 0){
  751             if(fwrite(lastcp, 1, i, f) != i)
  752                goto jerr;
  753             size += i;
  754          }
  755          for(lastcp = ++cp; *cp != '\0'; ++cp){
  756             if(*cp == '"')
  757                break;
  758             if(*cp == '\\' && cp[1] != '\0')
  759                ++cp;
  760          }
  761          /* We want to keep empty quoted-strings, too! */
  762          xin.s = n_UNCONST(lastcp);
  763          xin.l = P2UZ(cp - lastcp);
  764          if((x = a_mime__convhdra(&xin, f, colp, a_MIME_SH_QUOTE)) < 0)
  765             goto jerr;
  766          size += x;
  767          ++size;
  768          lastcp = &cp[1];
  769          break;
  770       }
  771    }
  772 
  773    i = P2UZ(cp - lastcp);
  774    if(i > 0){
  775       if(fwrite(lastcp, 1, i, f) != i)
  776          goto jerr;
  777       size += i;
  778    }
  779 jleave:
  780    NYD_OU;
  781    return size;
  782 jerr:
  783    size = -1;
  784    goto jleave;
  785 }
  786 
  787 static void
  788 _append_str(char **buf, uz *size, uz *pos, char const *str, uz len)
  789 {
  790    NYD_IN;
  791    *buf = n_realloc(*buf, *size += len);
  792    su_mem_copy(&(*buf)[*pos], str, len);
  793    *pos += len;
  794    NYD_OU;
  795 }
  796 
  797 static void
  798 _append_conv(char **buf, uz *size, uz *pos, char const *str, uz len)
  799 {
  800    struct str in, out;
  801    NYD_IN;
  802 
  803    in.s = n_UNCONST(str);
  804    in.l = len;
  805    mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
  806    _append_str(buf, size, pos, out.s, out.l);
  807    n_free(out.s);
  808    NYD_OU;
  809 }
  810 
  811 FL boole
  812 charset_iter_reset(char const *a_charset_to_try_first) /* TODO elim. dups! */
  813 {
  814    char const *sarr[3];
  815    uz sarrl[3], len;
  816    char *cp;
  817    NYD_IN;
  818    UNUSED(a_charset_to_try_first);
  819 
  820 #ifdef mx_HAVE_ICONV
  821    sarr[2] = ok_vlook(CHARSET_8BIT_OKEY);
  822 
  823    if(a_charset_to_try_first != NULL &&
  824          su_cs_cmp(a_charset_to_try_first, sarr[2]))
  825       sarr[0] = a_charset_to_try_first;
  826    else
  827       sarr[0] = NULL;
  828 
  829    if((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
  830          ok_blook(sendcharsets_else_ttycharset)){
  831       cp = n_UNCONST(ok_vlook(ttycharset));
  832       if(su_cs_cmp(cp, sarr[2]) && (sarr[0] == NULL || su_cs_cmp(cp, sarr[0])))
  833          sarr[1] = cp;
  834    }
  835 #else
  836    sarr[2] = ok_vlook(ttycharset);
  837 #endif
  838 
  839    sarrl[2] = len = su_cs_len(sarr[2]);
  840 #ifdef mx_HAVE_ICONV
  841    if ((cp = n_UNCONST(sarr[1])) != NULL)
  842       len += (sarrl[1] = su_cs_len(cp));
  843    else
  844       sarrl[1] = 0;
  845    if ((cp = n_UNCONST(sarr[0])) != NULL)
  846       len += (sarrl[0] = su_cs_len(cp));
  847    else
  848       sarrl[0] = 0;
  849 #endif
  850 
  851    _cs_iter_base = cp = n_autorec_alloc(len + 1 + 1 +1);
  852 
  853 #ifdef mx_HAVE_ICONV
  854    if ((len = sarrl[0]) != 0) {
  855       su_mem_copy(cp, sarr[0], len);
  856       cp[len] = ',';
  857       cp += ++len;
  858    }
  859    if ((len = sarrl[1]) != 0) {
  860       su_mem_copy(cp, sarr[1], len);
  861       cp[len] = ',';
  862       cp += ++len;
  863    }
  864 #endif
  865    len = sarrl[2];
  866    su_mem_copy(cp, sarr[2], len);
  867    cp[len] = '\0';
  868 
  869    _CS_ITER_STEP();
  870    NYD_OU;
  871    return (_cs_iter != NULL);
  872 }
  873 
  874 FL boole
  875 charset_iter_next(void)
  876 {
  877    boole rv;
  878    NYD_IN;
  879 
  880    _CS_ITER_STEP();
  881    rv = (_cs_iter != NULL);
  882    NYD_OU;
  883    return rv;
  884 }
  885 
  886 FL boole
  887 charset_iter_is_valid(void)
  888 {
  889    boole rv;
  890    NYD_IN;
  891 
  892    rv = (_cs_iter != NULL);
  893    NYD_OU;
  894    return rv;
  895 }
  896 
  897 FL char const *
  898 charset_iter(void)
  899 {
  900    char const *rv;
  901    NYD_IN;
  902 
  903    rv = _cs_iter;
  904    NYD_OU;
  905    return rv;
  906 }
  907 
  908 FL char const *
  909 charset_iter_or_fallback(void)
  910 {
  911    char const *rv;
  912    NYD_IN;
  913 
  914    rv = _CS_ITER_GET();
  915    NYD_OU;
  916    return rv;
  917 }
  918 
  919 FL void
  920 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
  921 {
  922    NYD_IN;
  923    outer_storage[0] = _cs_iter_base;
  924    outer_storage[1] = _cs_iter;
  925    NYD_OU;
  926 }
  927 
  928 FL void
  929 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
  930 {
  931    NYD_IN;
  932    _cs_iter_base = outer_storage[0];
  933    _cs_iter = outer_storage[1];
  934    NYD_OU;
  935 }
  936 
  937 #ifdef mx_HAVE_ICONV
  938 FL char const *
  939 need_hdrconv(struct header *hp) /* TODO once only, then iter */
  940 {
  941    struct n_header_field *hfp;
  942    char const *rv;
  943    NYD_IN;
  944 
  945    rv = NULL;
  946 
  947    /* C99 */{
  948       struct n_header_field *chlp[3]; /* TODO JOINED AFTER COMPOSE! */
  949       u32 i;
  950 
  951       chlp[0] = n_poption_arg_C;
  952       chlp[1] = n_customhdr_list;
  953       chlp[2] = hp->h_user_headers;
  954 
  955       for(i = 0; i < NELEM(chlp); ++i)
  956          if((hfp = chlp[i]) != NULL)
  957             do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
  958                goto jneeds;
  959             while((hfp = hfp->hf_next) != NULL);
  960    }
  961 
  962    if (hp->h_mft != NULL) {
  963       if (_name_highbit(hp->h_mft))
  964          goto jneeds;
  965    }
  966    if (hp->h_from != NULL) {
  967       if (_name_highbit(hp->h_from))
  968          goto jneeds;
  969    } else if (_has_highbit(myaddrs(NULL)))
  970       goto jneeds;
  971    if (hp->h_reply_to) {
  972       if (_name_highbit(hp->h_reply_to))
  973          goto jneeds;
  974    } else {
  975       char const *v15compat;
  976 
  977       if((v15compat = ok_vlook(replyto)) != NULL)
  978          n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
  979       if(_has_highbit(v15compat))
  980          goto jneeds;
  981       if(_has_highbit(ok_vlook(reply_to)))
  982          goto jneeds;
  983    }
  984    if (hp->h_sender) {
  985       if (_name_highbit(hp->h_sender))
  986          goto jneeds;
  987    } else if (_has_highbit(ok_vlook(sender)))
  988       goto jneeds;
  989 
  990    if (_name_highbit(hp->h_to))
  991       goto jneeds;
  992    if (_name_highbit(hp->h_cc))
  993       goto jneeds;
  994    if (_name_highbit(hp->h_bcc))
  995       goto jneeds;
  996    if (_has_highbit(hp->h_subject))
  997 jneeds:
  998       rv = _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
  999    NYD_OU;
 1000    return rv;
 1001 }
 1002 #endif /* mx_HAVE_ICONV */
 1003 
 1004 FL void
 1005 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
 1006 {
 1007    /* TODO mime_fromhdr(): is called with strings that contain newlines;
 1008     * TODO this is the usual newline problem all around the codebase;
 1009     * TODO i.e., if we strip it, then the display misses it ;>
 1010     * TODO this is why it is so messy and why S-nail v14.2 plus additional
 1011     * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
 1012     * TODO why our display reflects what is contained in the message: the 1:1
 1013     * TODO relationship of message content and display!
 1014     * TODO instead a header line should be decoded to what it is (a single
 1015     * TODO line that is) and it should be objective to the backend whether
 1016     * TODO it'll be folded to fit onto the display or not, e.g., for search
 1017     * TODO purposes etc.  then the only condition we have to honour in here
 1018     * TODO is that whitespace in between multiple adjacent MIME encoded words
 1019     * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
 1020     * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
 1021    struct str cin, cout;
 1022    char *p, *op, *upper;
 1023    u32 convert, lastenc, lastoutl;
 1024 #ifdef mx_HAVE_ICONV
 1025    char const *tcs;
 1026    char *cbeg;
 1027    iconv_t fhicd = (iconv_t)-1;
 1028 #endif
 1029    NYD_IN;
 1030 
 1031    out->l = 0;
 1032    if (in->l == 0) {
 1033       *(out->s = n_alloc(1)) = '\0';
 1034       goto jleave;
 1035    }
 1036    out->s = NULL;
 1037 
 1038 #ifdef mx_HAVE_ICONV
 1039    tcs = ok_vlook(ttycharset);
 1040 #endif
 1041    p = in->s;
 1042    upper = p + in->l;
 1043    lastenc = lastoutl = 0;
 1044 
 1045    while (p < upper) {
 1046       op = p;
 1047       if (*p == '=' && *(p + 1) == '?') {
 1048          p += 2;
 1049 #ifdef mx_HAVE_ICONV
 1050          cbeg = p;
 1051 #endif
 1052          while (p < upper && *p != '?')
 1053             ++p;  /* strip charset */
 1054          if (p >= upper)
 1055             goto jnotmime;
 1056          ++p;
 1057 #ifdef mx_HAVE_ICONV
 1058          if (flags & TD_ICONV) {
 1059             uz i = P2UZ(p - cbeg);
 1060             char *ltag, *cs = n_lofi_alloc(i);
 1061 
 1062             su_mem_copy(cs, cbeg, --i);
 1063             cs[i] = '\0';
 1064             /* RFC 2231 extends the RFC 2047 character set definition in
 1065              * encoded words by language tags - silently strip those off */
 1066             if ((ltag = su_cs_find_c(cs, '*')) != NULL)
 1067                *ltag = '\0';
 1068 
 1069             if (fhicd != (iconv_t)-1)
 1070                n_iconv_close(fhicd);
 1071             fhicd = su_cs_cmp_case(cs, tcs)
 1072                   ? n_iconv_open(tcs, cs) : (iconv_t)-1;
 1073             n_lofi_free(cs);
 1074          }
 1075 #endif
 1076          switch (*p) {
 1077          case 'B': case 'b':
 1078             convert = CONV_FROMB64;
 1079             break;
 1080          case 'Q': case 'q':
 1081             convert = CONV_FROMQP;
 1082             break;
 1083          default: /* invalid, ignore */
 1084             goto jnotmime;
 1085          }
 1086          if (*++p != '?')
 1087             goto jnotmime;
 1088          cin.s = ++p;
 1089          cin.l = 1;
 1090          for (;;) {
 1091             if (PCMP(p + 1, >=, upper))
 1092                goto jnotmime;
 1093             if (*p++ == '?' && *p == '=')
 1094                break;
 1095             ++cin.l;
 1096          }
 1097          ++p;
 1098          --cin.l;
 1099 
 1100          cout.s = NULL;
 1101          cout.l = 0;
 1102          if (convert == CONV_FROMB64) {
 1103             if(!b64_decode_header(&cout, &cin))
 1104                n_str_assign_cp(&cout, _("[Invalid Base64 encoding]"));
 1105          }else if(!qp_decode_header(&cout, &cin))
 1106             n_str_assign_cp(&cout, _("[Invalid Quoted-Printable encoding]"));
 1107          /* Normalize all decoded newlines to spaces XXX only \0/\n yet */
 1108          /* C99 */{
 1109             char const *xcp;
 1110             boole any;
 1111             uz i, j;
 1112 
 1113             for(any = FAL0, i = cout.l; i-- != 0;)
 1114                switch(cout.s[i]){
 1115                case '\0':
 1116                case '\n':
 1117                   any = TRU1;
 1118                   cout.s[i] = ' ';
 1119                   /* FALLTHRU */
 1120                default:
 1121                   break;
 1122 
 1123                }
 1124 
 1125             if(any){
 1126                /* I18N: must be non-empty, last must be closing bracket/xy */
 1127                xcp = _("[Content normalized: ]");
 1128                i = su_cs_len(xcp);
 1129                j = cout.l;
 1130                n_str_add_buf(&cout, xcp, i);
 1131                su_mem_move(&cout.s[i - 1], cout.s, j);
 1132                su_mem_copy(&cout.s[0], xcp, i - 1);
 1133                cout.s[cout.l - 1] = xcp[i - 1];
 1134             }
 1135          }
 1136 
 1137 
 1138          out->l = lastenc;
 1139 #ifdef mx_HAVE_ICONV
 1140          /* TODO Does not really work if we have assigned some ASCII or even
 1141           * TODO translated strings because of errors! */
 1142          if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
 1143             cin.s = NULL, cin.l = 0; /* XXX string pool ! */
 1144             convert = n_iconv_str(fhicd, n_ICONV_UNIDEFAULT, &cin, &cout, NULL);
 1145             out = n_str_add(out, &cin);
 1146             if (convert) {/* su_ERR_INVAL at EOS */
 1147                n_iconv_reset(fhicd);
 1148                out = n_str_add_buf(out, n_qm, 1); /* TODO unicode replacement */
 1149             }
 1150             n_free(cin.s);
 1151          } else
 1152 #endif
 1153             out = n_str_add(out, &cout);
 1154          lastenc = lastoutl = out->l;
 1155          n_free(cout.s);
 1156       } else
 1157 jnotmime: {
 1158          boole onlyws;
 1159 
 1160          p = op;
 1161          onlyws = (lastenc > 0);
 1162          for (;;) {
 1163             if (++op == upper)
 1164                break;
 1165             if (op[0] == '=' && (PCMP(op + 1, ==, upper) || op[1] == '?'))
 1166                break;
 1167             if (onlyws && !su_cs_is_blank(*op))
 1168                onlyws = FAL0;
 1169          }
 1170 
 1171          out = n_str_add_buf(out, p, P2UZ(op - p));
 1172          p = op;
 1173          if (!onlyws || lastoutl != lastenc)
 1174             lastenc = out->l;
 1175          lastoutl = out->l;
 1176       }
 1177    }
 1178    out->s[out->l] = '\0';
 1179 
 1180    if (flags & TD_ISPR) {
 1181       makeprint(out, &cout);
 1182       n_free(out->s);
 1183       *out = cout;
 1184    }
 1185    if (flags & TD_DELCTRL)
 1186       out->l = delctrl(out->s, out->l);
 1187 #ifdef mx_HAVE_ICONV
 1188    if (fhicd != (iconv_t)-1)
 1189       n_iconv_close(fhicd);
 1190 #endif
 1191 jleave:
 1192    NYD_OU;
 1193    return;
 1194 }
 1195 
 1196 FL char *
 1197 mime_fromaddr(char const *name)
 1198 {
 1199    char const *cp, *lastcp;
 1200    char *res = NULL;
 1201    uz ressz = 1, rescur = 0;
 1202    NYD_IN;
 1203 
 1204    if (name == NULL)
 1205       goto jleave;
 1206    if (*name == '\0') {
 1207       res = savestr(name);
 1208       goto jleave;
 1209    }
 1210 
 1211    if ((cp = routeaddr(name)) != NULL && cp > name) {
 1212       _append_conv(&res, &ressz, &rescur, name, P2UZ(cp - name));
 1213       lastcp = cp;
 1214    } else
 1215       cp = lastcp = name;
 1216 
 1217    for ( ; *cp; ++cp) {
 1218       switch (*cp) {
 1219       case '(':
 1220          _append_str(&res, &ressz, &rescur, lastcp, P2UZ(cp - lastcp + 1));
 1221          lastcp = ++cp;
 1222          cp = skip_comment(cp);
 1223          if (--cp > lastcp)
 1224             _append_conv(&res, &ressz, &rescur, lastcp, P2UZ(cp - lastcp));
 1225          lastcp = cp;
 1226          break;
 1227       case '"':
 1228          while (*cp) {
 1229             if (*++cp == '"')
 1230                break;
 1231             if (*cp == '\\' && cp[1] != '\0')
 1232                ++cp;
 1233          }
 1234          break;
 1235       }
 1236    }
 1237    if (cp > lastcp)
 1238       _append_str(&res, &ressz, &rescur, lastcp, P2UZ(cp - lastcp));
 1239    /* C99 */{
 1240       char *x;
 1241 
 1242       x = res;
 1243       res = savestrbuf(res, rescur);
 1244       if(x != NULL)
 1245          n_free(x);
 1246    }
 1247 jleave:
 1248    NYD_OU;
 1249    return res;
 1250 }
 1251 
 1252 FL sz
 1253 xmime_write(char const *ptr, uz size, FILE *f, enum conversion convert,
 1254    enum tdflags dflags, struct str * volatile outrest,
 1255    struct str * volatile inrest)
 1256 {
 1257    sz rv;
 1258    struct quoteflt *qf;
 1259    NYD_IN;
 1260 
 1261    quoteflt_reset(qf = quoteflt_dummy(), f);
 1262    rv = mime_write(ptr, size, f, convert, dflags, qf, outrest, inrest);
 1263    quoteflt_flush(qf);
 1264    NYD_OU;
 1265    return rv;
 1266 }
 1267 
 1268 static sigjmp_buf       __mimemw_actjmp; /* TODO someday.. */
 1269 static int              __mimemw_sig; /* TODO someday.. */
 1270 static n_sighdl_t  __mimemw_opipe;
 1271 static void
 1272 __mimemw_onsig(int sig) /* TODO someday, we won't need it no more */
 1273 {
 1274    NYD; /* Signal handler */
 1275    __mimemw_sig = sig;
 1276    siglongjmp(__mimemw_actjmp, 1);
 1277 }
 1278 
 1279 FL sz
 1280 mime_write(char const *ptr, uz size, FILE *f,
 1281    enum conversion convert, enum tdflags volatile dflags,
 1282    struct quoteflt *qf, struct str * volatile outrest,
 1283    struct str * volatile inrest)
 1284 {
 1285    /* TODO note: after send/MIME layer rewrite we will have a string pool
 1286     * TODO so that memory allocation count drops down massively; for now,
 1287     * TODO v14.0 that is, we pay a lot & heavily depend on the allocator.
 1288     * TODO P.S.: furthermore all this encapsulated in filter objects instead */
 1289    struct str in, out;
 1290    sz volatile xsize;
 1291    NYD_IN;
 1292 
 1293    dflags |= _TD_BUFCOPY;
 1294    in.s = n_UNCONST(ptr);
 1295    in.l = size;
 1296    out.s = NULL;
 1297    out.l = 0;
 1298 
 1299    if((xsize = size) == 0){
 1300       if(inrest != NULL && inrest->l != 0)
 1301          goto jinrest;
 1302       if(outrest != NULL && outrest->l != 0)
 1303          goto jconvert;
 1304       goto jleave;
 1305    }
 1306 
 1307    /* TODO This crap requires linewise input, then.  We need a filter chain
 1308     * TODO as in input->iconv->base64 where each filter can have its own
 1309     * TODO buffer, with a filter->fflush() call to get rid of those! */
 1310 #ifdef mx_HAVE_ICONV
 1311    if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
 1312          (convert == CONV_TOQP || convert == CONV_8BIT ||
 1313          convert == CONV_TOB64 || convert == CONV_TOHDR)) {
 1314       if (n_iconv_str(iconvd, n_ICONV_NONE, &out, &in, NULL) != 0) {
 1315          n_iconv_reset(iconvd);
 1316          /* TODO This causes hard-failure.  We would need to have an action
 1317           * TODO policy FAIL|IGNORE|SETERROR(but continue) */
 1318          xsize = -1;
 1319          goto jleave;
 1320       }
 1321       in = out;
 1322       out.s = NULL;
 1323       dflags &= ~_TD_BUFCOPY;
 1324    }
 1325 #endif
 1326 
 1327 jinrest:
 1328    if(inrest != NULL && inrest->l > 0){
 1329       if(size == 0){
 1330          in = *inrest;
 1331          inrest->s = NULL;
 1332          inrest->l = 0;
 1333       }else{
 1334          out.s = n_alloc(in.l + inrest->l + 1);
 1335          su_mem_copy(out.s, inrest->s, inrest->l);
 1336          if(in.l > 0)
 1337             su_mem_copy(&out.s[inrest->l], in.s, in.l);
 1338          if(in.s != ptr)
 1339             n_free(in.s);
 1340          (in.s = out.s)[in.l += inrest->l] = '\0';
 1341          inrest->l = 0;
 1342          out.s = NULL;
 1343       }
 1344       dflags &= ~_TD_BUFCOPY;
 1345    }
 1346 
 1347 jconvert:
 1348    __mimemw_sig = 0;
 1349    __mimemw_opipe = safe_signal(SIGPIPE, &__mimemw_onsig);
 1350    if (sigsetjmp(__mimemw_actjmp, 1))
 1351       goto jleave;
 1352 
 1353    switch (convert) {
 1354    case CONV_FROMQP:
 1355       if(!qp_decode_part(&out, &in, outrest, inrest)){
 1356          n_err(_("Invalid Quoted-Printable encoding ignored\n"));
 1357          xsize = 0; /* TODO size = -1 stops outer levels! */
 1358          break;
 1359       }
 1360       goto jqpb64_dec;
 1361    case CONV_TOQP:
 1362       if(qp_encode(&out, &in, QP_NONE) == NULL){
 1363          xsize = 0; /* TODO size = -1 stops outer levels! */
 1364          break;
 1365       }
 1366       goto jqpb64_enc;
 1367    case CONV_8BIT:
 1368       xsize = quoteflt_push(qf, in.s, in.l);
 1369       break;
 1370    case CONV_FROMB64:
 1371       if(!b64_decode_part(&out, &in, outrest, inrest))
 1372          goto jeb64;
 1373       outrest = NULL;
 1374       if(0){
 1375       /* FALLTHRU */
 1376    case CONV_FROMB64_T:
 1377          if(!b64_decode_part(&out, &in, outrest, inrest)){
 1378 jeb64:
 1379             n_err(_("Invalid Base64 encoding ignored\n"));
 1380             xsize = 0; /* TODO size = -1 stops outer levels! */
 1381             break;
 1382          }
 1383       }
 1384 jqpb64_dec:
 1385       if ((xsize = out.l) != 0)
 1386          xsize = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), outrest, qf);
 1387       break;
 1388    case CONV_TOB64:
 1389       /* TODO hack which is necessary unless this is a filter based approach
 1390        * TODO and each filter has its own buffer (as necessary): we must not
 1391        * TODO pass through a number of bytes which causes padding, otherwise we
 1392        * TODO produce multiple adjacent base64 streams, and that is not treated
 1393        * TODO in the same relaxed fashion like completely bogus bytes by at
 1394        * TODO least mutt and OpenSSL.  So we need an expensive workaround
 1395        * TODO unless we have input->iconv->base64 filter chain as such!! :( */
 1396       if(size != 0 && /* for Coverity, else ASSERT() */ inrest != NULL){
 1397          if(in.l > B64_ENCODE_INPUT_PER_LINE){
 1398             uz i;
 1399 
 1400             i = in.l % B64_ENCODE_INPUT_PER_LINE;
 1401             in.l -= i;
 1402 
 1403             if(i != 0){
 1404                ASSERT(inrest->l == 0);
 1405                inrest->s = n_realloc(inrest->s, i +1);
 1406                su_mem_copy(inrest->s, &in.s[in.l], i);
 1407                inrest->s[inrest->l = i] = '\0';
 1408             }
 1409          }else if(in.l < B64_ENCODE_INPUT_PER_LINE){
 1410             inrest->s = n_realloc(inrest->s, in.l +1);
 1411             su_mem_copy(inrest->s, in.s, in.l);
 1412             inrest->s[inrest->l = in.l] = '\0';
 1413             in.l = 0;
 1414             xsize = 0;
 1415             break;
 1416          }
 1417       }
 1418       if(b64_encode(&out, &in, B64_LF | B64_MULTILINE) == NULL){
 1419          xsize = -1;
 1420          break;
 1421       }
 1422 jqpb64_enc:
 1423       xsize = fwrite(out.s, sizeof *out.s, out.l, f);
 1424       if (xsize != (sz)out.l)
 1425          xsize = -1;
 1426       break;
 1427    case CONV_FROMHDR:
 1428       mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
 1429       xsize = quoteflt_push(qf, out.s, out.l);
 1430       break;
 1431    case CONV_TOHDR:
 1432       xsize = mime_write_tohdr(&in, f, NULL, a_MIME_SH_NONE);
 1433       break;
 1434    case CONV_TOHDR_A:{
 1435       uz col;
 1436 
 1437       if(dflags & _TD_BUFCOPY){
 1438          n_str_dup(&out, &in);
 1439          in = out;
 1440          out.s = NULL;
 1441          dflags &= ~_TD_BUFCOPY;
 1442       }
 1443       col = 0;
 1444       xsize = mime_write_tohdr_a(&in, f, &col, a_MIME_SH_NONE);
 1445       }break;
 1446    default:
 1447       xsize = _fwrite_td(&in, dflags, NULL, qf);
 1448       break;
 1449    }
 1450 
 1451 jleave:
 1452    if (out.s != NULL)
 1453       n_free(out.s);
 1454    if (in.s != ptr)
 1455       n_free(in.s);
 1456    safe_signal(SIGPIPE, __mimemw_opipe);
 1457    if (__mimemw_sig != 0)
 1458       n_raise(__mimemw_sig);
 1459    NYD_OU;
 1460    return xsize;
 1461 }
 1462 
 1463 #include "su/code-ou.h"
 1464 /* s-it-mode */