"Fossies" - the Fresh Open Source Software Archive

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


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

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ Implementation of mailcap.h.
    3  *@ TODO - We do not support the additional formats '%n' and '%F' (manual!).
    4  *@ TODO - We do not support handlers for multipart MIME parts (manual!).
    5  *@ TODO - We only support viewing/quoting (+ implications on fmt expansion).
    6  *@ TODO - With an on_loop_tick_event, trigger cache update once per loop max.
    7  *@ TODO   (or, if we ever get there, use a path_monitor: for all such!)
    8  *
    9  * Copyright (c) 2019 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
   10  * SPDX-License-Identifier: ISC
   11  *
   12  * Permission to use, copy, modify, and/or distribute this software for any
   13  * purpose with or without fee is hereby granted, provided that the above
   14  * copyright notice and this permission notice appear in all copies.
   15  *
   16  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   17  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   18  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   19  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   20  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   21  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   22  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   23  */
   24 #undef su_FILE
   25 #define su_FILE mailcap
   26 #define mx_SOURCE
   27 #define mx_SOURCE_MAILCAP
   28 
   29 #ifndef mx_HAVE_AMALGAMATION
   30 # include "mx/nail.h"
   31 #endif
   32 
   33 su_EMPTY_FILE()
   34 #ifdef mx_HAVE_MAILCAP
   35 #include "su/cs.h"
   36 #include "su/cs-dict.h"
   37 #include "su/mem.h"
   38 
   39 #include "mx/child.h"
   40 #include "mx/cmd.h"
   41 #include "mx/file-streams.h"
   42 #include "mx/mime-type.h"
   43 
   44 #include "mx/mailcap.h"
   45 #include "su/code-in.h"
   46 
   47 /* Whether we should try to place as much on a line as possible (Y).
   48  * Otherwise (X) we place commands on lines of their own */
   49 #define a_MAILCAP_DUMP_SEP_INJ(X,Y) X /* Y */
   50 
   51 /* Dictionary stores a_mailcap_hdl* list, not owned */
   52 #define a_MAILCAP_CSD_FLAGS (su_CS_DICT_CASE | su_CS_DICT_HEAD_RESORT |\
   53       su_CS_DICT_ERR_PASS)
   54 #define a_MAILCAP_CSD_TRESHOLD_SHIFT 3
   55 
   56 /* Must be alphabetical */
   57 enum a_mailcap_sfields{
   58    a_MAILCAP_SF_CMD,
   59    a_MAILCAP_SF_COMPOSE,
   60    a_MAILCAP_SF_COMPOSETYPED,
   61    a_MAILCAP_SF_DESCRIPTION,
   62    a_MAILCAP_SF_EDIT,
   63    a_MAILCAP_SF_NAMETEMPLATE,
   64    a_MAILCAP_SF_PRINT,
   65    a_MAILCAP_SF_TEST,
   66    a_MAILCAP_SF_X11_BITMAP
   67 };
   68 CTAV(a_MAILCAP_SF_CMD == 0);
   69 enum {a_MAILCAP_SF_MAX = a_MAILCAP_SF_X11_BITMAP + 1};
   70 
   71 /* sfields we really handle, less test (no format expansion in there) */
   72 #define a_MAILCAP_SFIELD_SUPPORTED(X) \
   73    ((X) == a_MAILCAP_SF_CMD /*|| (X) == a_MAILCAP_SF_TEST*/)
   74 
   75 enum a_mailcap_flags{
   76    a_MAILCAP_F_TEXTUALNEWLINES = mx_MIMETYPE_HDL_MAX << 1,
   77    a_MAILCAP_F_TESTONCE = mx_MIMETYPE_HDL_MAX << 2,
   78    a_MAILCAP_F_TEST_ONCE_DONE = mx_MIMETYPE_HDL_MAX << 3,
   79    a_MAILCAP_F_TEST_ONCE_SUCCESS = mx_MIMETYPE_HDL_MAX << 4,
   80    a_MAILCAP_F_HAS_S_FORMAT = mx_MIMETYPE_HDL_MAX << 5 /* Somewhere a %s */
   81 };
   82 enum {a_MAILCAP_F_MAX = a_MAILCAP_F_HAS_S_FORMAT};
   83 CTA(a_MAILCAP_F_MAX <= S32_MAX,
   84    "a_mailcap_hdl.mch_flags bit range excessed");
   85 
   86 struct a_mailcap_hdl{
   87    struct a_mailcap_hdl *mch_next;
   88    BITENUM_IS(u32,mx_mimetype_handler_flags) mch_flags;
   89    u8 mch__pad[2];
   90    /* All strings are placed in successive memory after "self".
   91     * Since mch_cmd always exists 0 is the invalid offset for the rest.
   92     * The sum of all strings fits in S32_MAX.
   93     * sfield_has_format is a bitset */
   94    u16 mch_sfield_has_format;
   95    u32 mch_sfields[a_MAILCAP_SF_MAX];
   96 };
   97 
   98 struct a_mailcap_load_stack{
   99    char const *mcls_name;
  100    char const *mcls_name_quoted; /* Messages somewhat common, just do it. */
  101    FILE *mcls_fp;
  102    char const *mcls_type_subtype;
  103    struct str mcls_dat;
  104    struct str mcls_conti_dat;
  105    uz mcls_conti_len;
  106    /* Preparated handler; during preparation string data is temporarily stored
  107     * in .mcls_hdl_buf */
  108    struct a_mailcap_hdl mcls_hdl;
  109    struct n_string mcls_hdl_buf;
  110 };
  111 
  112 static struct su_cs_dict *a_mailcap_dp, a_mailcap__d; /* XXX atexit _gut() */
  113 
  114 /* We stop parsing and _gut(FAL0) on hard errors like NOMEM, OVERFLOW and IO.
  115  * The __parse*() series return is-error, with TRUM1 being a fatal one */
  116 static void a_mailcap_create(void);
  117 
  118 static boole a_mailcap__load_file(struct a_mailcap_load_stack *mclsp);
  119 static boole a_mailcap__parse_line(struct a_mailcap_load_stack *mclsp,
  120       char *dp, uz dl);
  121 static boole a_mailcap__parse_kv(struct a_mailcap_load_stack *mclsp,
  122       char *kp, char *vp);
  123 static boole a_mailcap__parse_value(u32 sfield,
  124       struct a_mailcap_load_stack *mclsp, struct str *s);
  125 static boole a_mailcap__parse_flag(struct a_mailcap_load_stack *mclsp,
  126       char *flag);
  127 static boole a_mailcap__parse_create_hdl(struct a_mailcap_load_stack *mclsp,
  128       struct a_mailcap_hdl **ins_or_nil);
  129 
  130 static void a_mailcap_gut(boole gut_dp);
  131 
  132 /* */
  133 static struct n_strlist *a_mailcap_dump(char const *cmdname, char const *key,
  134       void const *dat);
  135 
  136 static void a_mailcap__dump_kv(u32 sfield, struct n_string *s, uz *llp,
  137       char const *pre, char const *vp);
  138 static struct n_string *a_mailcap__dump_quote(struct n_string *s,
  139       char const *cp, boole quotequote);
  140 
  141 /* Expand a command string with embedded formats */
  142 static char const *a_mailcap_expand_formats(char const *format,
  143       struct mimepart const *mpp, char const *ct);
  144 
  145 static void
  146 a_mailcap_create(void){
  147    struct a_mailcap_load_stack mcls;
  148    char *cp_base, *cp;
  149    NYD_IN;
  150 
  151    a_mailcap_dp = su_cs_dict_set_treshold_shift(
  152             su_cs_dict_create(&a_mailcap__d, a_MAILCAP_CSD_FLAGS, NIL),
  153          a_MAILCAP_CSD_TRESHOLD_SHIFT);
  154 
  155    if(*(cp_base = UNCONST(char*,ok_vlook(MAILCAPS))) == '\0')
  156       goto jleave;
  157 
  158    su_mem_set(&mcls, 0, sizeof mcls);
  159    mx_fs_linepool_aquire(&mcls.mcls_dat.s, &mcls.mcls_dat.l);
  160    n_string_book(n_string_creat(&mcls.mcls_hdl_buf), 248); /* ovflw not yet */
  161 
  162    for(cp_base = savestr(cp_base);
  163          (cp = su_cs_sep_c(&cp_base, ':', TRU1)) != NIL;){
  164       if((cp = fexpand(cp, (FEXP_NOPROTO | FEXP_LOCAL_FILE | FEXP_NSHELL))
  165             ) == NIL)
  166          continue;
  167 
  168       mcls.mcls_name_quoted = n_shexp_quote_cp(cp, FAL0);
  169       if((mcls.mcls_fp = mx_fs_open(mcls.mcls_name = cp, "r")) == NIL){
  170          s32 eno;
  171 
  172          if((eno = su_err_no()) != su_ERR_NOENT)
  173             n_err(_("$MAILCAPS: cannot open %s: %s\n"),
  174                mcls.mcls_name_quoted, su_err_doc(eno));
  175          continue;
  176       }
  177 
  178       if(!a_mailcap__load_file(&mcls))
  179          cp = NIL;
  180 
  181       mx_fs_close(mcls.mcls_fp);
  182 
  183       if(cp == NIL){
  184          a_mailcap_gut(FAL0);
  185          break;
  186       }
  187    }
  188 
  189    if(mcls.mcls_conti_dat.s != NIL)
  190       mx_fs_linepool_release(mcls.mcls_conti_dat.s, mcls.mcls_conti_dat.l);
  191    mx_fs_linepool_release(mcls.mcls_dat.s, mcls.mcls_dat.l);
  192 
  193    n_string_gut(&mcls.mcls_hdl_buf);
  194 
  195 jleave:
  196    NYD_OU;
  197 }
  198 
  199 static boole
  200 a_mailcap__load_file(struct a_mailcap_load_stack *mclsp){
  201    enum{a_NONE, a_CONTI = 1u<<0, a_EOF = 1u<<1, a_NEWCONTI = 1u<<2};
  202    char const *emsg;
  203    uz len;
  204    u32 f;
  205    NYD2_IN;
  206 
  207    emsg = NIL;
  208 
  209    for(f = a_NONE;;){
  210       if(fgetline(&mclsp->mcls_dat.s, &mclsp->mcls_dat.l, NIL, &len,
  211             mclsp->mcls_fp, TRU1) == NIL){
  212          if(ferror(mclsp->mcls_fp)){
  213             emsg = N_("I/O error");
  214             goto jerr;
  215          }
  216          f |= a_EOF;
  217          if(f & a_CONTI)
  218             goto jconti_do;
  219          break;
  220       }
  221       ASSERT(len > 0);
  222       mclsp->mcls_dat.s[--len] = '\0';
  223 
  224       /* Is it a comment?  Must be in first column, cannot be continued */
  225       if(!(f & a_CONTI) && len > 0 && mclsp->mcls_dat.s[0] == '#')
  226          continue;
  227 
  228       /* Is it a continuation line?  It really is for an uneven number of \ */
  229       f &= ~a_NEWCONTI;
  230       if(len > 0 && mclsp->mcls_dat.s[len - 1] == '\\'){
  231          uz i, j;
  232 
  233          if(len == 1)
  234             continue;
  235          else for(j = 1, i = len - 1; i-- > 0; ++j)
  236             if(mclsp->mcls_dat.s[i] != '\\'){
  237                if(j & 1){
  238                   f |= a_NEWCONTI;
  239                   --len;
  240                }
  241                break;
  242             }
  243       }
  244 
  245       /* Necessary to create/append to continuation line storage? */
  246       if(f & (a_CONTI | a_NEWCONTI)){
  247          if(mclsp->mcls_conti_dat.s == NIL){
  248             mclsp->mcls_conti_dat = mclsp->mcls_dat;
  249             mclsp->mcls_conti_len = len;
  250             mx_fs_linepool_aquire(&mclsp->mcls_dat.s, &mclsp->mcls_dat.l);
  251          }else{
  252             if(!mx_fs_linepool_book(&mclsp->mcls_conti_dat.s,
  253                   &mclsp->mcls_conti_dat.l, mclsp->mcls_conti_len,
  254                   MAX(len, 256)))
  255                goto jetoolong;
  256             su_mem_copy(&mclsp->mcls_conti_dat.s[mclsp->mcls_conti_len],
  257                mclsp->mcls_dat.s, len +1);
  258             mclsp->mcls_conti_len += len;
  259          }
  260          f |= a_CONTI;
  261 
  262          if(f & a_NEWCONTI)
  263             continue;
  264 
  265 jconti_do:
  266          /* C99 */{
  267             boole x;
  268 
  269             x = a_mailcap__parse_line(mclsp, mclsp->mcls_conti_dat.s,
  270                   mclsp->mcls_conti_len);
  271             /* Release the buffer to the linepool, like that we can swap it in
  272              * again the next time, shall this be necessary! */
  273             mx_fs_linepool_release(mclsp->mcls_conti_dat.s,
  274                mclsp->mcls_conti_dat.l);
  275             mclsp->mcls_conti_dat.s = NIL;
  276 
  277             switch(x){
  278             case FAL0: break;
  279             case TRU1: break;
  280             case TRUM1: goto jenomem;
  281             }
  282          }
  283          if((f ^= a_CONTI) & a_EOF)
  284             break;
  285       }else switch(a_mailcap__parse_line(mclsp, mclsp->mcls_dat.s, len)){
  286       case FAL0: break;
  287       case TRU1: break;
  288       case TRUM1: goto jenomem;
  289       }
  290    }
  291 
  292 jleave:
  293    NYD2_OU;
  294    return (emsg == NIL);
  295 
  296 jenomem:
  297    emsg = N_("out of memory");
  298    goto jerr;
  299 jetoolong:
  300    su_state_err(su_STATE_ERR_OVERFLOW, (su_STATE_ERR_PASS |
  301       su_STATE_ERR_NOERRNO), _("$MAILCAPS: line too long"));
  302    emsg = N_("line too long");
  303 jerr:
  304    n_err(_("$MAILCAPS: %s while loading %s\n"),
  305       V_(emsg), mclsp->mcls_name_quoted);
  306    goto jleave;
  307 }
  308 
  309 static boole
  310 a_mailcap__parse_line(struct a_mailcap_load_stack *mclsp, char *dp, uz dl){
  311    struct str s;
  312    union {void *v; struct a_mailcap_hdl *mch; struct a_mailcap_hdl **pmch;} p;
  313    char *cp, *cp2, *key;
  314    uz rnd;
  315    boole rv;
  316    NYD2_IN;
  317 
  318    su_mem_set(&mclsp->mcls_hdl, 0, sizeof(mclsp->mcls_hdl));
  319    n_string_trunc(&mclsp->mcls_hdl_buf, 0);
  320 
  321    rv = TRU1;
  322 
  323    if(dl == 0)
  324       goto jleave;
  325 
  326    s.s = dp;
  327    s.l = dl;
  328    if(n_str_trim(&s, n_STR_TRIM_BOTH)->l == 0){
  329       if(n_poption & n_PO_D_V)
  330          n_err(_("$MAILCAPS: %s: line empty after whitespace removal "
  331                "(invalid RFC 1524 syntax)\n"), mclsp->mcls_name_quoted);
  332       goto jleave;
  333    }else if(s.l >= S32_MAX){
  334       /* As stated, the sum must fit in S32_MAX */
  335       rv = TRUM1;
  336       goto jleave;
  337    }
  338    (dp = s.s)[s.l] = '\0';
  339 
  340    rnd = 0;
  341    UNINIT(key, NIL);
  342    UNINIT(p.v, NIL);
  343 
  344    while((cp = su_cs_sep_escable_c(&dp, ';', FAL0)) != NIL){
  345       /* We do not allow empty fields, but there may be a trailing semicolon */
  346       if(*cp == '\0' && dp == NIL)
  347          break;
  348 
  349       /* First: TYPE/SUBTYPE; separate them first */
  350       if(++rnd == 1){
  351          if(*cp == '\0'){
  352             key = UNCONST(char*,N_("no MIME TYPE"));
  353             goto jerr;
  354          }else if((cp2 = su_cs_find_c(cp, '/')) == NIL){
  355 jesubtype:
  356             n_err(_("$MAILCAPS: %s: missing SUBTYPE, assuming /* (any): %s\n"),
  357                mclsp->mcls_name_quoted, cp);
  358             cp2 = UNCONST(char*,n_star);
  359          }else{
  360             *cp2++ = '\0';
  361             if(*cp2 == '\0')
  362                goto jesubtype;
  363          }
  364 
  365          /* And unite for the real one */
  366          key = savecatsep(cp, '/', cp2);
  367          if(!mx_mimetype_is_valid(key, TRU1, TRU1)){
  368             cp = key;
  369             key = UNCONST(char*,N_("invalid MIME type"));
  370             goto jerr;
  371          }
  372          mclsp->mcls_type_subtype = key;
  373 
  374          if((p.v = su_cs_dict_lookup(a_mailcap_dp, key)) != NIL){
  375             while(p.mch->mch_next != NIL)
  376                p.mch = p.mch->mch_next;
  377             p.pmch = &p.mch->mch_next;
  378          }
  379       }
  380       /* The mandatory view command */
  381       else if(rnd == 2){
  382          s.l = su_cs_len(s.s = cp);
  383          n_str_trim(&s, n_STR_TRIM_BOTH);
  384          if((rv = a_mailcap__parse_value(a_MAILCAP_SF_CMD, mclsp, &s)))
  385             goto jleave;
  386       }
  387       /* An optional field */
  388       else{
  389          if(*cp == '\0'){
  390             if(n_poption & n_PO_D_V)
  391                n_err(_("$MAILCAPS: %s: ignoring empty optional field: %s\n"),
  392                   mclsp->mcls_name_quoted, key);
  393          }else if((cp2 = su_cs_find_c(cp, '=')) != NIL){
  394             *cp2++ = '\0';
  395             if((rv = a_mailcap__parse_kv(mclsp, cp, cp2)))
  396                goto jleave;
  397          }else if(a_mailcap__parse_flag(mclsp, cp))
  398             goto jleave;
  399       }
  400    }
  401 
  402    rv = a_mailcap__parse_create_hdl(mclsp, p.pmch);
  403 
  404 jleave:
  405    NYD2_OU;
  406    return rv;
  407 
  408 jerr:
  409    n_err(_("$MAILCAPS: %s: skip due to error: %s: %s\n"),
  410       mclsp->mcls_name_quoted, V_(key), cp);
  411    rv = TRU1;
  412    goto jleave;
  413 }
  414 
  415 static boole
  416 a_mailcap__parse_kv(struct a_mailcap_load_stack *mclsp, char *kp, char *vp){
  417 #undef a_X
  418 #define a_X(X,Y) FIELD_INITI(su_CONCAT(a_MAILCAP_SF_, X) - 1) su_STRING(Y)
  419    static char const sfa[][16] = {
  420       a_X(COMPOSE, compose),
  421       a_X(COMPOSETYPED, composetyped),
  422       a_X(DESCRIPTION, description),
  423       a_X(EDIT, edit),
  424       a_X(NAMETEMPLATE, nametemplate),
  425       a_X(PRINT, print),
  426       a_X(TEST, test),
  427       a_X(X11_BITMAP, x11-bitmap)
  428    };
  429 #undef a_X
  430 
  431    struct str s;
  432    char **cpp;
  433    char const *emsg, (*sfapp)[16];
  434    boole rv;
  435    NYD2_IN;
  436 
  437    /* Trim key and value */
  438    rv = TRU1;
  439    emsg = R(char*,1);
  440    cpp = &kp;
  441 jredo:
  442    s.l = su_cs_len(s.s = *cpp);
  443    if(n_str_trim(&s, n_STR_TRIM_BOTH)->l == 0){
  444       emsg = (emsg == R(char*,1)) ? N_("ignored: empty key")
  445             : N_("ignored: empty value");
  446       goto jerr;
  447    }
  448    (*cpp = s.s)[s.l] = '\0';
  449 
  450    if(emsg == R(char*,1)){
  451       emsg = R(char*,-1);
  452       cpp = &vp;
  453       goto jredo;
  454    }
  455 
  456    emsg = NIL;
  457 
  458    /* Find keyword */
  459    for(sfapp = &sfa[0]; sfapp < &sfa[NELEM(sfa)]; ++sfapp){
  460       if(!su_cs_cmp_case(kp, *sfapp)){
  461          uz i;
  462 
  463          ASSERT(s.s == vp);
  464          i = P2UZ(sfapp - &sfa[0]) + 1;
  465 
  466          if((n_poption & n_PO_D_V) && mclsp->mcls_hdl.mch_sfields[i] != 0)
  467             n_err(_("$MAILCAPS: %s: %s: multiple %s fields\n"),
  468                mclsp->mcls_name_quoted, mclsp->mcls_type_subtype, kp);
  469 
  470          switch(i){
  471          default:
  472             break;
  473          case a_MAILCAP_SF_DESCRIPTION:
  474             /* This is "optionally quoted" */
  475             if(*vp == '"'){
  476                s.s = ++vp;
  477                --s.l;
  478                if(s.s[s.l - 1] == '"')
  479                   s.s[--s.l] = '\0';
  480                else
  481                   emsg = N_("unclosed quoted description");
  482             }
  483             break;
  484 
  485          case a_MAILCAP_SF_NAMETEMPLATE:{
  486             char *cp, c;
  487 
  488             if((cp = vp)[0] != '%' || cp[1] != 's'){
  489 jentempl:
  490                emsg = N_("unsatisfied constraints, ignoring nametemplate");
  491                goto jerr;
  492             }
  493             for(cp += 2; (c = *cp++) != '\0';)
  494                if(!su_cs_is_alnum(c) && c != '_' && c != '.')
  495                   goto jentempl;
  496             }
  497             break;
  498          }
  499 
  500          mclsp->mcls_hdl.mch_sfields[i] = mclsp->mcls_hdl_buf.s_len;
  501          if((rv = a_mailcap__parse_value(i, mclsp, &s)))
  502             mclsp->mcls_hdl.mch_sfields[i] = 0;
  503 
  504          if(emsg != NIL)
  505             goto jerr;
  506          goto jleave;
  507       }
  508    }
  509 
  510    if((rv = (kp[0] != 'x' && kp[0] != '\0' && kp[1] != '-')) ||
  511          (n_poption & n_PO_D_V)){
  512       emsg = N_("ignored unknown string/command");
  513       goto jerr;
  514    }
  515 
  516    rv = FAL0;
  517 jleave:
  518    NYD2_OU;
  519    return rv;
  520 
  521 jerr:
  522    /* I18N: FILENAME: TYPE/SUBTYPE: ERROR MSG: key = value */
  523    n_err(_("$MAILCAPS: %s: %s: %s: %s = %s\n"),
  524       mclsp->mcls_name_quoted, mclsp->mcls_type_subtype, V_(emsg), kp, vp);
  525    goto jleave;
  526 }
  527 
  528 static boole
  529 a_mailcap__parse_value(u32 sfield, struct a_mailcap_load_stack *mclsp,
  530       struct str *s){
  531    char *cp2, *cp3, c;
  532    boole rv;
  533    NYD2_IN;
  534 
  535    if(S(uz,S32_MAX) - mclsp->mcls_hdl_buf.s_len <= s->l +1){
  536       rv = TRUM1;
  537       goto jleave;
  538    }
  539 
  540    rv = FAL0;
  541 
  542    /* Take over unless we see a format, then branch to more expensive code */
  543    for(cp2 = cp3 = s->s;;){
  544       c = *cp2++;
  545       if(c == '\\')
  546          c = *cp2++;
  547       else if(c == '%'){
  548          if(*cp2 == '%')
  549             ++cp2;
  550          else{
  551             --cp2;
  552             goto jfmt;
  553          }
  554       }
  555 
  556       if((*cp3++ = c) == '\0')
  557          break;
  558    }
  559 
  560    n_string_push_c(n_string_push_buf(&mclsp->mcls_hdl_buf,
  561       s->s, P2UZ(cp3 - s->s)), '\0');
  562 
  563 jleave:
  564    NYD2_OU;
  565    return rv;
  566 
  567 jfmt:{
  568    char *lbuf;
  569 
  570    /* C99 */{
  571       uz i;
  572 
  573       lbuf = n_lofi_alloc(s->l * 2);
  574       i = P2UZ(cp3 - s->s);
  575       su_mem_copy(lbuf, s->s, i);
  576       cp3 = &lbuf[i];
  577    }
  578 
  579    for(;;){
  580       c = *cp2++;
  581       if(c == '\\')
  582          c = *cp2++;
  583       else if(c == '%'){
  584          switch((c = *cp2++)){
  585          case '{':
  586             if(su_cs_find_c(cp2, '}') == NIL){
  587                n_err(_("$MAILCAPS: %s: %s: unclosed %%{ format: %s\n"),
  588                   mclsp->mcls_name_quoted, mclsp->mcls_type_subtype, s->s);
  589                /* We need to skip the entire thing if we are incapable to
  590                 * satisfy the format request when invoking a command */
  591                if(a_MAILCAP_SFIELD_SUPPORTED(sfield)){
  592                   rv = TRU1;
  593                   goto jfmt_leave;
  594                }
  595                goto jquote;
  596             }
  597             /* FALLTHRU */
  598             if(0){
  599          case 's':
  600                if(sfield == a_MAILCAP_SF_TEST){ /* XXX only view/quote */
  601                   n_err(_("$MAILCAPS: %s: %s: %%s format cannot be used in "
  602                         "the \"test\" field\n"),
  603                      mclsp->mcls_name_quoted, mclsp->mcls_type_subtype, s->s);
  604                   rv = TRU1;
  605                   goto jfmt_leave;
  606                }
  607                /* xxx Very primitive user-used-false-quotes check */
  608                if((n_poption & n_PO_D_V) && (*cp2 == '"' || *cp2 == '\''))
  609                   n_err(_("$MAILCAPS: %s: %s: (maybe!) "
  610                         "%%s must not be quoted: %s\n"),
  611                      mclsp->mcls_name_quoted, mclsp->mcls_type_subtype, s->s);
  612 
  613                mclsp->mcls_hdl.mch_flags |= a_MAILCAP_F_HAS_S_FORMAT;
  614             }
  615             /* FALLTHRU */
  616          case 't':
  617             mclsp->mcls_hdl.mch_sfield_has_format |= 1u << sfield;
  618             *cp3++ = '\0';
  619             break;
  620 
  621          case 'n':
  622             /* FALLTHRU */
  623          case 'F':
  624             n_err(_("$MAILCAPS: %s: %s: unsupported format %%%c\n"),
  625                mclsp->mcls_name_quoted, mclsp->mcls_type_subtype, c);
  626             /* We need to skip the entire thing if we are incapable to satisfy
  627              * the format request when invoking a command */
  628             if(a_MAILCAP_SFIELD_SUPPORTED(sfield)){
  629                rv = TRU1;
  630                goto jfmt_leave;
  631             }
  632             /* Since it is actually ignored, do not care, do not quote */
  633             mclsp->mcls_hdl.mch_sfield_has_format |= 1u << sfield;
  634             *cp3++ = '\0';
  635             break;
  636 
  637          default:
  638             n_err(_("$MAILCAPS: %s: %s: invalid format %%%c, "
  639                   "should be quoted: \\%%%c\n"),
  640                mclsp->mcls_name_quoted, mclsp->mcls_type_subtype, c, c);
  641 jquote:
  642             --cp2;
  643             c = '%';
  644             break;
  645          }
  646       }
  647 
  648       if((*cp3++ = c) == '\0')
  649          break;
  650    }
  651 
  652    n_string_push_c(n_string_push_buf(&mclsp->mcls_hdl_buf,
  653       lbuf, P2UZ(cp3 - lbuf)), '\0');
  654 
  655 jfmt_leave:
  656    n_lofi_free(lbuf);
  657    }
  658    goto jleave;
  659 }
  660 
  661 static boole
  662 a_mailcap__parse_flag(struct a_mailcap_load_stack *mclsp, char *flag){
  663    static struct{
  664       BITENUM_IS(u32,mx_mimetype_handler_flags) flags;
  665       char name[28];
  666    } const *fap, fa[] = {
  667       /* In manual order */
  668       {mx_MIMETYPE_HDL_COPIOUSOUTPUT, "copiousoutput"},
  669       {mx_MIMETYPE_HDL_NEEDSTERM, "needsterminal"},
  670       {a_MAILCAP_F_TEXTUALNEWLINES, "textualnewlines"},
  671       {mx_MIMETYPE_HDL_ASYNC, "x-mailx-async"},
  672       {mx_MIMETYPE_HDL_NOQUOTE, "x-mailx-noquote"},
  673       {a_MAILCAP_F_TESTONCE, "x-mailx-test-once"},
  674       {mx_MIMETYPE_HDL_TMPF, "x-mailx-tmpfile"},
  675       {mx_MIMETYPE_HDL_TMPF_FILL, "x-mailx-tmpfile-fill"},
  676       {mx_MIMETYPE_HDL_TMPF_UNLINK, "x-mailx-tmpfile-unlink\0"}
  677    };
  678    boole rv;
  679    NYD2_IN;
  680 
  681    rv = FAL0;
  682 
  683    for(fap = fa; fap < &fa[NELEM(fa)]; ++fap)
  684       if(!su_cs_cmp_case(flag, fap->name)){
  685          if((n_poption & n_PO_D_V) && (mclsp->mcls_hdl.mch_flags & fap->flags))
  686             n_err(_("$MAILCAPS: %s: %s: multiple %s flags\n"),
  687                mclsp->mcls_name_quoted, mclsp->mcls_type_subtype, flag);
  688          mclsp->mcls_hdl.mch_flags |= fap->flags;
  689          goto jleave;
  690       }
  691 
  692    if((rv = (flag[0] != 'x' && flag[0] != '\0' && flag[1] != '-')) ||
  693          (n_poption & n_PO_D_V))
  694       n_err(_("$MAILCAPS: %s: %s: ignored unknown flag: %s\n"),
  695          mclsp->mcls_name_quoted, mclsp->mcls_type_subtype, flag);
  696 
  697 jleave:
  698    NYD2_OU;
  699    return rv;
  700 }
  701 
  702 static boole
  703 a_mailcap__parse_create_hdl(struct a_mailcap_load_stack *mclsp,
  704       struct a_mailcap_hdl **ins_or_nil){
  705    struct a_mailcap_hdl *mchp;
  706    char const *emsg;
  707    u32 f;
  708    boole rv;
  709    NYD2_IN;
  710 
  711    rv = TRU1;
  712 
  713    /* Flag implications */
  714 
  715    f = mclsp->mcls_hdl.mch_flags;
  716 
  717    if(f & (mx_MIMETYPE_HDL_TMPF_FILL | mx_MIMETYPE_HDL_TMPF_UNLINK))
  718       f |= mx_MIMETYPE_HDL_TMPF;
  719 
  720    if(f & mx_MIMETYPE_HDL_ASYNC){
  721       if(f & mx_MIMETYPE_HDL_COPIOUSOUTPUT){
  722          emsg = N_("cannot use x-mailx-async and copiousoutput");
  723          goto jerr;
  724       }
  725       if(f & mx_MIMETYPE_HDL_TMPF_UNLINK){
  726          emsg = N_("cannot use x-mailx-async and x-mailx-tmpfile-unlink");
  727          goto jerr;
  728       }
  729    }
  730 
  731    if(f & mx_MIMETYPE_HDL_NEEDSTERM){
  732       if(f & mx_MIMETYPE_HDL_COPIOUSOUTPUT){
  733          emsg = N_("cannot use needsterminal and copiousoutput");
  734          goto jerr;
  735       }
  736       if(f & mx_MIMETYPE_HDL_ASYNC){
  737          emsg = N_("cannot use needsterminal and x-mailx-async");
  738          goto jerr;
  739       }
  740    }
  741 
  742    /* Mailcap implications */
  743 
  744    if(mclsp->mcls_hdl.mch_sfields[a_MAILCAP_SF_NAMETEMPLATE] != 0){
  745       if(f & a_MAILCAP_F_HAS_S_FORMAT)
  746          f |= mx_MIMETYPE_HDL_TMPF_NAMETMPL |
  747                mx_MIMETYPE_HDL_TMPF_NAMETMPL_SUFFIX;
  748       else
  749          n_err(_("$MAILCAPS: %s: %s: no %%s format, ignoring nametemplate\n"),
  750             mclsp->mcls_name_quoted, mclsp->mcls_type_subtype);
  751    }
  752 
  753    if(f & mx_MIMETYPE_HDL_TMPF){
  754       /* Not with any %s */
  755       if(f & a_MAILCAP_F_HAS_S_FORMAT){
  756          emsg = N_("cannot use x-mailx-tmpfile if formats use %s");
  757          goto jerr;
  758       }
  759    }
  760 
  761    mclsp->mcls_hdl.mch_flags = f;
  762 
  763    /* Since all strings altogether fit in S32_MAX, allocate one big chunk */
  764    rv = TRUM1;
  765 
  766    mchp = S(struct a_mailcap_hdl*,su_CALLOC(sizeof(*mchp) +
  767             mclsp->mcls_hdl_buf.s_len +1));
  768    if(mchp == NIL)
  769       goto jleave;
  770 
  771    su_mem_copy(mchp, &mclsp->mcls_hdl, sizeof(mclsp->mcls_hdl));
  772    su_mem_copy(&mchp[1], n_string_cp(&mclsp->mcls_hdl_buf),
  773       mclsp->mcls_hdl_buf.s_len +1);
  774 
  775    rv = FAL0;
  776 
  777    if(ins_or_nil != NIL)
  778       *ins_or_nil = mchp;
  779    else if(su_cs_dict_insert(a_mailcap_dp, mclsp->mcls_type_subtype, mchp) > 0)
  780       rv = TRUM1;
  781 
  782 jleave:
  783    NYD2_OU;
  784    return rv;
  785 
  786 jerr:
  787    n_err(_("$MAILCAPS: %s: %s: %s\n"),
  788       mclsp->mcls_name_quoted, mclsp->mcls_type_subtype, V_(emsg));
  789    goto jleave;
  790 }
  791 
  792 static void
  793 a_mailcap_gut(boole gut_dp){
  794    NYD2_IN;
  795 
  796    if(a_mailcap_dp != NIL){
  797       struct su_cs_dict_view csdv;
  798 
  799       su_CS_DICT_FOREACH(a_mailcap_dp, &csdv){
  800          struct a_mailcap_hdl *mchp, *tmp;
  801 
  802          for(mchp = S(struct a_mailcap_hdl*,su_cs_dict_view_data(&csdv));
  803                mchp != NIL;){
  804             tmp = mchp;
  805             mchp = mchp->mch_next;
  806             su_FREE(tmp);
  807          }
  808       }
  809 
  810       if(gut_dp){
  811          su_cs_dict_gut(a_mailcap_dp);
  812          a_mailcap_dp = NIL;
  813       }else
  814          su_cs_dict_clear(a_mailcap_dp);
  815    }
  816 
  817    NYD2_OU;
  818 }
  819 
  820 static struct n_strlist *
  821 a_mailcap_dump(char const *cmdname, char const *key, void const *dat){
  822    /* XXX real strlist + str_to_fmt() */
  823 #undef a_X
  824 #define a_X(X,Y) FIELD_INITI(su_CONCAT(a_MAILCAP_SF_, X)) Y
  825    static char const sfa[][20] = {
  826       a_X(CMD, " "),
  827       a_X(COMPOSE, " compose = "),
  828       a_X(COMPOSETYPED, " composetyped = \0"),
  829       a_X(DESCRIPTION, " description = "),
  830       a_X(EDIT, " edit = "),
  831       a_X(NAMETEMPLATE, " nametemplate = "),
  832       a_X(PRINT, " print = "),
  833       a_X(TEST, " test = "),
  834       a_X(X11_BITMAP, " x11-bitmap = ")
  835    };
  836 #undef a_X
  837 
  838    static struct{
  839       u32 a_f;
  840       char a_n[28];
  841    } const fa[] = {
  842       {mx_MIMETYPE_HDL_COPIOUSOUTPUT, " copiousoutput"},
  843       {mx_MIMETYPE_HDL_NEEDSTERM, " needsterminal"},
  844       {a_MAILCAP_F_TEXTUALNEWLINES, " textualnewlines\0"},
  845       {mx_MIMETYPE_HDL_ASYNC, " x-mailx-async"},
  846       {mx_MIMETYPE_HDL_NOQUOTE, " x-mailx-noquote"},
  847       {a_MAILCAP_F_TESTONCE, " x-mailx-test-once"},
  848       {mx_MIMETYPE_HDL_TMPF, " x-mailx-tmpfile"},
  849       {mx_MIMETYPE_HDL_TMPF_FILL, " x-mailx-tmpfile-fill"},
  850       {mx_MIMETYPE_HDL_TMPF_UNLINK, " x-mailx-tmpfile-unlink\0"}
  851    };
  852 
  853    struct n_string s_b, *s;
  854    struct a_mailcap_hdl const *mchp;
  855    struct n_strlist *slp;
  856    NYD2_IN;
  857    UNUSED(cmdname);
  858 
  859    s = n_string_book(n_string_creat_auto(&s_b), 127);
  860    s = n_string_resize(s, n_STRLIST_PLAIN_SIZE());
  861 
  862    for(mchp = S(struct a_mailcap_hdl const*,dat); mchp != NIL;
  863          mchp = mchp->mch_next){
  864       uz i, lo, lx;
  865       char const *buf;
  866 
  867       if(S(void const*,mchp) != dat)
  868          s = n_string_push_c(s, '\n');
  869 
  870       lo = i = su_cs_len(key);
  871       s = n_string_push_buf(s, key, i);
  872 
  873       buf = S(char const*,&mchp[1]);
  874       for(i = a_MAILCAP_SF_CMD; i < NELEM(sfa); ++i)
  875          if(i == a_MAILCAP_SF_CMD || mchp->mch_sfields[i] > 0)
  876             a_mailcap__dump_kv(i, s, &lo, sfa[i], &buf[mchp->mch_sfields[i]]);
  877 
  878       if(mchp->mch_flags != 0){
  879          a_MAILCAP_DUMP_SEP_INJ(boole any = FAL0, (void)0);
  880 
  881          for(i = 0; i < NELEM(fa); ++i){
  882             if(mchp->mch_flags & fa[i].a_f){
  883                uz j;
  884 
  885                s = n_string_push_c(s, ';');
  886                ++lo;
  887                j = su_cs_len(fa[i].a_n);
  888                if(a_MAILCAP_DUMP_SEP_INJ(!any, FAL0) || lo + j >= 76){
  889                   s = n_string_push_buf(s, "\\\n ", sizeof("\\\n ") -1);
  890                   lo = 1;
  891                }
  892                lx = s->s_len;
  893                s = n_string_push_buf(s, fa[i].a_n, j);
  894                lo += s->s_len - lx;
  895                a_MAILCAP_DUMP_SEP_INJ(any = TRU1, (void)0);
  896             }
  897          }
  898       }
  899    }
  900 
  901    s = n_string_push_c(s, '\n');
  902 
  903    n_string_cp(s);
  904 
  905    slp = R(struct n_strlist*,S(void*,s->s_dat));
  906    /* xxx Should we assert alignment constraint of slp is satisfied?
  907     * xxx Should be, heap memory with alignment < sizeof(void*) bitter? */
  908    slp->sl_next = NIL;
  909    slp->sl_len = s->s_len;
  910    n_string_drop_ownership(s);
  911 
  912    NYD2_OU;
  913    return slp;
  914 }
  915 
  916 static void
  917 a_mailcap__dump_kv(u32 sfield, struct n_string *s, uz *llp, char const *pre,
  918       char const *vp){
  919    boole quote;
  920    uz lo, prel, i, lx;
  921    NYD2_IN;
  922 
  923    lo = *llp;
  924    prel = su_cs_len(pre);
  925 
  926    s = n_string_push_c(s, ';');
  927    ++lo;
  928 
  929    for(i = 0;; ++i)
  930       if(vp[i] == '\0' && vp[i + 1] == '\0')
  931          break;
  932    /* An empty command is an error if no other field follows, a condition we do
  933     * not know here, so put something; instead of a dummy field, put success */
  934    if(i == 0 && sfield == a_MAILCAP_SF_CMD){
  935       vp = ":\0\0";
  936       i = 1;
  937    }
  938 
  939    if(a_MAILCAP_DUMP_SEP_INJ(sfield != a_MAILCAP_SF_CMD || lo + prel + i >= 75,
  940          lo + prel + i >= 72)){
  941       s = n_string_push_buf(s, "\\\n ", sizeof("\\\n ") -1);
  942       lo = 1;
  943    }
  944 
  945    quote = (sfield == a_MAILCAP_SF_DESCRIPTION);
  946 
  947    lx = s->s_len;
  948    s = n_string_push_buf(s, pre, prel);
  949 
  950    if(quote && i > 0 && (su_cs_is_space(vp[0]) || su_cs_is_space(vp[i - 1])))
  951       s = n_string_push_c(s, '"');
  952    else
  953       quote = FAL0;
  954    s = a_mailcap__dump_quote(s, vp, quote);
  955    if(quote)
  956       s = n_string_push_c(s, '"');
  957 
  958    lo += s->s_len - lx;
  959    *llp = lo;
  960    NYD2_OU;
  961 }
  962 
  963 static struct n_string *
  964 a_mailcap__dump_quote(struct n_string *s, char const *cp, boole quotequote){
  965    char c;
  966    NYD2_IN;
  967 
  968    for(;;){
  969       if((c = *cp++) == '\0'){
  970          if((c = *cp++) == '\0')
  971             break;
  972          s = n_string_push_c(s, '%');
  973       }else if(c == ';' || c == '\\' || c == '%' || (c == '"' && quotequote))
  974          s = n_string_push_c(s, '\\');
  975       s = n_string_push_c(s, c);
  976    }
  977 
  978    NYD2_OU;
  979    return s;
  980 }
  981 
  982 static char const *
  983 a_mailcap_expand_formats(char const *format, struct mimepart const *mpp,
  984       char const *ct){
  985    struct n_string s_b, *s = &s_b;
  986    char const *cp, *xp;
  987    NYD2_IN;
  988 
  989    s = n_string_creat_auto(s);
  990    s = n_string_book(s, 128);
  991 
  992    for(cp = format;;){
  993       char c;
  994 
  995       if((c = *cp++) == '\0'){
  996          if((c = *cp++) == '\0')
  997             break;
  998 
  999          switch(c){
 1000          case '{':
 1001             s = n_string_push_c(s, '\'');
 1002             xp = su_cs_find_c(cp, '}');
 1003             ASSERT(xp != NIL); /* (parser) */
 1004             xp = savestrbuf(cp, P2UZ(xp - cp));
 1005             if((xp = mime_param_get(xp, mpp->m_ct_type)) != NIL){
 1006                /* XXX Maybe we should simply shell quote that thing? */
 1007                while((c = *xp++) != '\0'){
 1008                   if(c != '\'')
 1009                      s = n_string_push_c(s, c);
 1010                   else
 1011                      s = n_string_push_cp(s, "'\"'\"'");
 1012                }
 1013             }
 1014             s = n_string_push_c(s, '\'');
 1015             break;
 1016          case 's':
 1017             /* For that we leave the actual expansion up to $SHELL.
 1018              * See XXX comment in mx_mailcap_handler(), however */
 1019             s = n_string_push_cp(s,
 1020                   "\"${" n_PIPEENV_FILENAME_TEMPORARY "}\"");
 1021             break;
 1022          case 't':
 1023             s = n_string_push_cp(s, ct);
 1024             break;
 1025          }
 1026       }else
 1027          s = n_string_push_c(s, c);
 1028    }
 1029 
 1030    cp = n_string_cp(s);
 1031    n_string_drop_ownership(s);
 1032 
 1033    NYD2_OU;
 1034    return cp;
 1035 }
 1036 
 1037 int
 1038 c_mailcap(void *vp){
 1039    boole load_only;
 1040    char **argv;
 1041    NYD_IN;
 1042 
 1043    argv = vp;
 1044 
 1045    load_only = FAL0;
 1046    if(*argv == NIL)
 1047       goto jlist;
 1048    if(argv[1] != NIL)
 1049       goto jerr;
 1050    if(su_cs_starts_with_case("show", *argv))
 1051       goto jlist;
 1052 
 1053    load_only = TRU1;
 1054    if(su_cs_starts_with_case("load", *argv))
 1055       goto jlist;
 1056    if(su_cs_starts_with_case("clear", *argv)){
 1057       a_mailcap_gut(TRU1);
 1058       goto jleave;
 1059    }
 1060 
 1061 jerr:
 1062    mx_cmd_print_synopsis(mx_cmd_firstfit("mailcap"), NIL);
 1063    vp = NIL;
 1064 jleave:
 1065    NYD_OU;
 1066    return (vp == NIL ? n_EXIT_ERR : n_EXIT_OK);
 1067 
 1068 jlist:
 1069    if(a_mailcap_dp == NIL)
 1070       a_mailcap_create();
 1071 
 1072    if(!load_only){
 1073       struct n_strlist *slp;
 1074 
 1075       slp = NIL;
 1076       if(!(mx_xy_dump_dict("mailcap", a_mailcap_dp, &slp, NIL,
 1077                &a_mailcap_dump) &&
 1078             mx_page_or_print_strlist("mailcap", slp, TRU1)))
 1079          vp = NIL;
 1080    }
 1081    goto jleave;
 1082 }
 1083 
 1084 boole
 1085 mx_mailcap_handler(struct mx_mimetype_handler *mthp, char const *ct,
 1086       enum sendaction action, struct mimepart const *mpp){
 1087    union {void *v; char const *c; struct a_mailcap_hdl *mch;} p;
 1088    boole wildcard;
 1089    NYD_IN;
 1090    UNUSED(mpp);
 1091 
 1092    /* For now we support that only, too */
 1093    ASSERT_NYD_EXEC(action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
 1094       action == SEND_TODISP || action == SEND_TODISP_ALL ||
 1095       action == SEND_TODISP_PARTS, mthp = NIL);
 1096 
 1097    if(ok_blook(mailcap_disable))
 1098       goto jleave0;
 1099 
 1100    if(a_mailcap_dp == NIL)
 1101       a_mailcap_create();
 1102    if(su_cs_dict_count(a_mailcap_dp) == 0)
 1103       goto jleave0;
 1104 
 1105    /* Walk over the list of handlers and check whether one fits */
 1106    wildcard = FAL0;
 1107    p.v = su_cs_dict_lookup(a_mailcap_dp, ct);
 1108 jagain:
 1109    for(; p.mch != NIL; p.mch = p.mch->mch_next){
 1110       u32 f;
 1111 
 1112       f = p.mch->mch_flags;
 1113 
 1114       if(action == SEND_TODISP || action == SEND_TODISP_ALL){
 1115          /*if(f & mx_MIMETYPE_HDL_ASYNC)
 1116           *   continue;*/
 1117          if(!(f & mx_MIMETYPE_HDL_COPIOUSOUTPUT))
 1118             continue;
 1119       }else if(action == SEND_QUOTE || action == SEND_QUOTE_ALL){
 1120          if(f & mx_MIMETYPE_HDL_NOQUOTE)
 1121             continue;
 1122          /*if(f & mx_MIMETYPE_HDL_ASYNC)
 1123           *   continue;*/
 1124          if(f & mx_MIMETYPE_HDL_NEEDSTERM) /* XXX for now */
 1125             continue;
 1126          if(!(f & mx_MIMETYPE_HDL_COPIOUSOUTPUT)) /* xxx for now */
 1127             continue;
 1128       }else{
 1129          /* `mimeview' */
 1130       }
 1131 
 1132       /* Flags seem to fit, do we need to test? */
 1133       if(p.mch->mch_sfields[a_MAILCAP_SF_TEST] != 0){
 1134          if(!(f & a_MAILCAP_F_TESTONCE) || !(f & a_MAILCAP_F_TEST_ONCE_DONE)){
 1135             struct mx_child_ctx cc;
 1136 
 1137             mx_child_ctx_setup(&cc);
 1138             cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE;
 1139             cc.cc_cmd = ok_vlook(SHELL);
 1140             cc.cc_args[0] = "-c";
 1141             cc.cc_args[1] = &R(char const*,&p.mch[1]
 1142                   )[p.mch->mch_sfields[a_MAILCAP_SF_TEST]];
 1143             if(p.mch->mch_sfield_has_format & (1u << a_MAILCAP_SF_TEST))
 1144                cc.cc_args[1] = a_mailcap_expand_formats(cc.cc_args[1],
 1145                      mpp, ct);
 1146 
 1147             if(mx_child_run(&cc) && cc.cc_exit_status == n_EXIT_OK)
 1148                f |= a_MAILCAP_F_TEST_ONCE_SUCCESS;
 1149 
 1150             if(f & a_MAILCAP_F_TESTONCE){
 1151                f |= a_MAILCAP_F_TEST_ONCE_DONE;
 1152                p.mch->mch_flags = f;
 1153             }
 1154          }
 1155 
 1156          if(!(f & a_MAILCAP_F_TEST_ONCE_SUCCESS))
 1157             continue;
 1158       }
 1159 
 1160       /* That one shall be it */
 1161       if(f & a_MAILCAP_F_HAS_S_FORMAT){
 1162          f |= mx_MIMETYPE_HDL_TMPF | mx_MIMETYPE_HDL_TMPF_FILL;
 1163          if(!(f & mx_MIMETYPE_HDL_ASYNC))
 1164             f |= mx_MIMETYPE_HDL_TMPF_UNLINK;
 1165       }
 1166       f |= mx_MIMETYPE_HDL_CMD;
 1167       mthp->mth_flags = f;
 1168 
 1169       /* XXX We could use a different policy where the handler has a callback
 1170        * XXX mechanism that is called when the handler's environment is fully
 1171        * XXX setup; mailcap could use that to expand mth_shell_cmd.
 1172        * XXX For now simply embed MAILX_FILENAME_TEMPORARY, and leave expansion
 1173        * XXX up to the shell */
 1174       mthp->mth_shell_cmd = &R(char const*,&p.mch[1]
 1175             )[p.mch->mch_sfields[a_MAILCAP_SF_CMD]];
 1176       if(p.mch->mch_sfield_has_format & (1u << a_MAILCAP_SF_CMD))
 1177          mthp->mth_shell_cmd = a_mailcap_expand_formats(mthp->mth_shell_cmd,
 1178                mpp, ct);
 1179 
 1180       if(p.mch->mch_sfields[a_MAILCAP_SF_NAMETEMPLATE] != 0){
 1181          mthp->mth_tmpf_nametmpl = &R(char const*,&p.mch[1]
 1182                )[p.mch->mch_sfields[a_MAILCAP_SF_NAMETEMPLATE]];
 1183          ASSERT(mthp->mth_tmpf_nametmpl[0] == '\0' &&
 1184             mthp->mth_tmpf_nametmpl[1] == 's');
 1185          mthp->mth_tmpf_nametmpl += 2;
 1186       }
 1187       goto jleave;
 1188    }
 1189 
 1190    /* Direct match, otherwise try wildcard match? */
 1191    if(!wildcard && (p.c = su_cs_find_c(ct, '/')) != NIL){
 1192       char *cp;
 1193       uz i;
 1194 
 1195       wildcard = TRU1;
 1196       cp = n_lofi_alloc((i = P2UZ(p.c - ct)) + 2 +1);
 1197 
 1198       su_mem_copy(cp, ct, i);
 1199       cp[i++] = '/';
 1200       cp[i++] = '*';
 1201       cp[i++] = '\0';
 1202       p.v = su_cs_dict_lookup(a_mailcap_dp, cp);
 1203 
 1204       n_lofi_free(cp);
 1205 
 1206       if(p.v != NIL)
 1207          goto jagain;
 1208    }
 1209 
 1210 jleave0:
 1211    mthp = NIL;
 1212 jleave:
 1213    NYD_OU;
 1214    return (mthp != NIL);
 1215 }
 1216 
 1217 #include "su/code-ou.h"
 1218 #endif /* mx_HAVE_MAILCAP */
 1219 /* s-it-mode */