"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.10/mime.c" (25 Mar 2018, 44472 Bytes) of package /linux/misc/s-nail-14.9.10.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.9_vs_14.9.10.

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