"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.11/mime.c" (8 Aug 2018, 44451 Bytes) of package /linux/misc/s-nail-14.9.11.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.10_vs_14.9.11.

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