"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.7/mime.c" (16 Feb 2018, 43527 Bytes) of package /linux/misc/s-nail-14.9.7.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.6_vs_14.9.7.

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