"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.10/shexp.c" (25 Mar 2018, 65784 Bytes) of package /linux/misc/s-nail-14.9.10.tar.xz:


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

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ Shell "word", file- and other name expansions, incl. file globbing.
    3  *@ TODO v15: peek signal states while opendir/readdir/etc.
    4  *@ TODO "Magic solidus" used as path separator.
    5  *
    6  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
    7  * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
    8  */
    9 /*
   10  * Copyright (c) 1980, 1993
   11  *      The Regents of the University of California.  All rights reserved.
   12  *
   13  * Redistribution and use in source and binary forms, with or without
   14  * modification, are permitted provided that the following conditions
   15  * are met:
   16  * 1. Redistributions of source code must retain the above copyright
   17  *    notice, this list of conditions and the following disclaimer.
   18  * 2. Redistributions in binary form must reproduce the above copyright
   19  *    notice, this list of conditions and the following disclaimer in the
   20  *    documentation and/or other materials provided with the distribution.
   21  * 3. Neither the name of the University nor the names of its contributors
   22  *    may be used to endorse or promote products derived from this software
   23  *    without specific prior written permission.
   24  *
   25  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
   26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
   29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   35  * SUCH DAMAGE.
   36  */
   37 #undef n_FILE
   38 #define n_FILE shexp
   39 
   40 #ifndef HAVE_AMALGAMATION
   41 # include "nail.h"
   42 #endif
   43 
   44 #include <pwd.h>
   45 
   46 #ifdef HAVE_FNMATCH
   47 # include <dirent.h>
   48 # include <fnmatch.h>
   49 #endif
   50 
   51 /* POSIX says
   52  *   Environment variable names used by the utilities in the Shell and
   53  *   Utilities volume of POSIX.1-2008 consist solely of uppercase
   54  *   letters, digits, and the <underscore> ('_') from the characters
   55  *   defined in Portable Character Set and do not begin with a digit.
   56  *   Other characters may be permitted by an implementation;
   57  *   applications shall tolerate the presence of such names.
   58  * We do support the hyphen-minus "-" (except in last position for ${x[:]-y}).
   59  * We support some special parameter names for one-letter(++) variable names;
   60  * these have counterparts in the code that manages internal variables,
   61  * and some more special treatment below! */
   62 #define a_SHEXP_ISVARC(C) (alnumchar(C) || (C) == '_' || (C) == '-')
   63 #define a_SHEXP_ISVARC_BAD1ST(C) (digitchar(C)) /* (Actually assumed below!) */
   64 #define a_SHEXP_ISVARC_BADNST(C) ((C) == '-')
   65 
   66 enum a_shexp_quote_flags{
   67    a_SHEXP_QUOTE_NONE,
   68    a_SHEXP_QUOTE_ROUNDTRIP = 1u<<0, /* Result won't be consumed immediately */
   69 
   70    a_SHEXP_QUOTE_T_REVSOL = 1u<<8,  /* Type: by reverse solidus */
   71    a_SHEXP_QUOTE_T_SINGLE = 1u<<9,  /* Type: single-quotes */
   72    a_SHEXP_QUOTE_T_DOUBLE = 1u<<10, /* Type: double-quotes */
   73    a_SHEXP_QUOTE_T_DOLLAR = 1u<<11, /* Type: dollar-single-quotes */
   74    a_SHEXP_QUOTE_T_MASK = a_SHEXP_QUOTE_T_REVSOL | a_SHEXP_QUOTE_T_SINGLE |
   75          a_SHEXP_QUOTE_T_DOUBLE | a_SHEXP_QUOTE_T_DOLLAR,
   76 
   77    a_SHEXP_QUOTE__FREESHIFT = 16u
   78 };
   79 
   80 #ifdef HAVE_FNMATCH
   81 struct a_shexp_glob_ctx{
   82    char const *sgc_patdat;       /* Remaining pattern (at and below level) */
   83    size_t sgc_patlen;
   84    struct n_string *sgc_outer;   /* Resolved path up to this level */
   85    ui32_t sgc_flags;
   86    ui8_t sgc__dummy[4];
   87 };
   88 #endif
   89 
   90 struct a_shexp_quote_ctx{
   91    struct n_string *sqc_store;   /* Result storage */
   92    struct str sqc_input;         /* Input data, topmost level */
   93    ui32_t sqc_cnt_revso;
   94    ui32_t sqc_cnt_single;
   95    ui32_t sqc_cnt_double;
   96    ui32_t sqc_cnt_dollar;
   97    enum a_shexp_quote_flags sqc_flags;
   98    ui8_t sqc__dummy[4];
   99 };
  100 
  101 struct a_shexp_quote_lvl{
  102    struct a_shexp_quote_lvl *sql_link; /* Outer level */
  103    struct str sql_dat;                 /* This level (has to) handle(d) */
  104    enum a_shexp_quote_flags sql_flags;
  105    ui8_t sql__dummy[4];
  106 };
  107 
  108 /* Locate the user's mailbox file (where new, unread mail is queued) */
  109 static char *a_shexp_findmail(char const *user, bool_t force);
  110 
  111 /* Expand ^~/? and ^~USER/? constructs.
  112  * Returns the completely resolved (maybe empty or identical to input)
  113  * salloc()ed string */
  114 static char *a_shexp_tilde(char const *s);
  115 
  116 /* Perform fnmatch(3).  May return NULL on error */
  117 static char *a_shexp_globname(char const *name, enum fexp_mode fexpm);
  118 #ifdef HAVE_FNMATCH
  119 static bool_t a_shexp__glob(struct a_shexp_glob_ctx *sgcp,
  120                struct n_strlist **slpp);
  121 static int a_shexp__globsort(void const *cvpa, void const *cvpb);
  122 #endif
  123 
  124 /* Parse an input string and create a sh(1)ell-quoted result */
  125 static void a_shexp__quote(struct a_shexp_quote_ctx *sqcp,
  126                struct a_shexp_quote_lvl *sqlp);
  127 
  128 static char *
  129 a_shexp_findmail(char const *user, bool_t force){
  130    char *rv;
  131    char const *cp;
  132    NYD2_ENTER;
  133 
  134    if(!force){
  135       if((cp = ok_vlook(inbox)) != NULL && *cp != '\0'){
  136          /* _NFOLDER extra introduced to avoid % recursion loops */
  137          if((rv = fexpand(cp, FEXP_NSPECIAL | FEXP_NFOLDER | FEXP_NSHELL)
  138                ) != NULL)
  139             goto jleave;
  140          n_err(_("*inbox* expansion failed, using $MAIL / built-in: %s\n"), cp);
  141       }
  142       /* Heirloom compatibility: an IMAP *folder* becomes "%" */
  143 #ifdef HAVE_IMAP
  144       else if(cp == NULL && !strcmp(user, ok_vlook(LOGNAME)) &&
  145             which_protocol(cp = n_folder_query(), FAL0, FAL0, NULL)
  146                == PROTO_IMAP){
  147          /* TODO Compat handling of *folder* with IMAP! */
  148          n_OBSOLETE("no more expansion of *folder* in \"%\": "
  149             "please set *inbox*");
  150          rv = savestr(cp);
  151          goto jleave;
  152       }
  153 #endif
  154 
  155       if((cp = ok_vlook(MAIL)) != NULL){
  156          rv = savestr(cp);
  157          goto jleave;
  158       }
  159    }
  160 
  161    /* C99 */{
  162       size_t ul, i;
  163 
  164       ul = strlen(user) +1;
  165       i = sizeof(VAL_MAIL) -1 + 1 + ul;
  166 
  167       rv = salloc(i);
  168       memcpy(rv, VAL_MAIL, (i = sizeof(VAL_MAIL) -1));
  169       rv[i] = '/';
  170       memcpy(&rv[++i], user, ul);
  171    }
  172 jleave:
  173    NYD2_LEAVE;
  174    return rv;
  175 }
  176 
  177 static char *
  178 a_shexp_tilde(char const *s){
  179    struct passwd *pwp;
  180    size_t nl, rl;
  181    char const *rp, *np;
  182    char *rv;
  183    NYD2_ENTER;
  184 
  185    if(*(rp = &s[1]) == '/' || *rp == '\0'){
  186       np = ok_vlook(HOME);
  187       rl = strlen(rp);
  188    }else{
  189       if((rp = strchr(np = rp, '/')) != NULL){
  190          nl = PTR2SIZE(rp - np);
  191          np = savestrbuf(np, nl);
  192          rl = strlen(rp);
  193       }else
  194          rl = 0;
  195 
  196       if((pwp = getpwnam(np)) == NULL){
  197          rv = savestr(s);
  198          goto jleave;
  199       }
  200       np = pwp->pw_dir;
  201    }
  202 
  203    nl = strlen(np);
  204    rv = salloc(nl + 1 + rl +1);
  205    memcpy(rv, np, nl);
  206    if(rl > 0){
  207       memcpy(rv + nl, rp, rl);
  208       nl += rl;
  209    }
  210    rv[nl] = '\0';
  211 jleave:
  212    NYD2_LEAVE;
  213    return rv;
  214 }
  215 
  216 static char *
  217 a_shexp_globname(char const *name, enum fexp_mode fexpm){
  218 #ifdef HAVE_FNMATCH
  219    struct a_shexp_glob_ctx sgc;
  220    struct n_string outer;
  221    struct n_strlist *slp;
  222    char *cp;
  223    NYD_ENTER;
  224 
  225    memset(&sgc, 0, sizeof sgc);
  226    sgc.sgc_patlen = strlen(name);
  227    sgc.sgc_patdat = savestrbuf(name, sgc.sgc_patlen);
  228    sgc.sgc_outer = n_string_reserve(n_string_creat(&outer), sgc.sgc_patlen);
  229    sgc.sgc_flags = ((fexpm & FEXP_SILENT) != 0);
  230    slp = NULL;
  231    if(a_shexp__glob(&sgc, &slp))
  232       cp = (char*)1;
  233    else
  234       cp = NULL;
  235    n_string_gut(&outer);
  236 
  237    if(cp == NULL)
  238       goto jleave;
  239 
  240    if(slp == NULL){
  241       cp = n_UNCONST(N_("File pattern does not match"));
  242       goto jerr;
  243    }else if(slp->sl_next == NULL)
  244       cp = savestrbuf(slp->sl_dat, slp->sl_len);
  245    else if(fexpm & FEXP_MULTIOK){
  246       struct n_strlist **sorta, *xslp;
  247       size_t i, no, l;
  248 
  249       no = l = 0;
  250       for(xslp = slp; xslp != NULL; xslp = xslp->sl_next){
  251          ++no;
  252          l += xslp->sl_len + 1;
  253       }
  254 
  255       sorta = smalloc(sizeof(*sorta) * no);
  256       no = 0;
  257       for(xslp = slp; xslp != NULL; xslp = xslp->sl_next)
  258          sorta[no++] = xslp;
  259       qsort(sorta, no, sizeof *sorta, &a_shexp__globsort);
  260 
  261       cp = salloc(++l);
  262       l = 0;
  263       for(i = 0; i < no; ++i){
  264          xslp = sorta[i];
  265          memcpy(&cp[l], xslp->sl_dat, xslp->sl_len);
  266          l += xslp->sl_len;
  267          cp[l++] = '\0';
  268       }
  269       cp[l] = '\0';
  270 
  271       free(sorta);
  272       n_pstate |= n_PS_EXPAND_MULTIRESULT;
  273    }else{
  274       cp = n_UNCONST(N_("File pattern matches multiple results"));
  275       goto jerr;
  276    }
  277 
  278 jleave:
  279    while(slp != NULL){
  280       struct n_strlist *tmp = slp;
  281 
  282       slp = slp->sl_next;
  283       free(tmp);
  284    }
  285    NYD_LEAVE;
  286    return cp;
  287 
  288 jerr:
  289    if(!(fexpm & FEXP_SILENT)){
  290       name = n_shexp_quote_cp(name, FAL0);
  291       n_err("%s: %s\n", V_(cp), name);
  292    }
  293    cp = NULL;
  294    goto jleave;
  295 
  296 #else /* HAVE_FNMATCH */
  297    n_UNUSED(fexpm);
  298 
  299    if(!(fexpm & FEXP_SILENT))
  300       n_err(_("No filename pattern (fnmatch(3)) support compiled in\n"));
  301    return savestr(name);
  302 #endif
  303 }
  304 
  305 #ifdef HAVE_FNMATCH
  306 static bool_t
  307 a_shexp__glob(struct a_shexp_glob_ctx *sgcp, struct n_strlist **slpp){
  308    enum{a_SILENT = 1<<0, a_DEEP=1<<1, a_SALLOC=1<<2};
  309 
  310    struct a_shexp_glob_ctx nsgc;
  311    struct dirent *dep;
  312    DIR *dp;
  313    size_t old_outerlen;
  314    char const *ccp, *myp;
  315    NYD2_ENTER;
  316 
  317    /* We need some special treatment for the outermost level.
  318     * All along our way, normalize path separators */
  319    if(!(sgcp->sgc_flags & a_DEEP)){
  320       if(sgcp->sgc_patlen > 0 && sgcp->sgc_patdat[0] == '/'){
  321          myp = n_string_cp(n_string_push_c(sgcp->sgc_outer, '/'));
  322          do
  323             ++sgcp->sgc_patdat;
  324          while(--sgcp->sgc_patlen > 0 && sgcp->sgc_patdat[0] == '/');
  325       }else
  326          myp = "./";
  327    }else
  328       myp = n_string_cp(sgcp->sgc_outer);
  329    old_outerlen = sgcp->sgc_outer->s_len;
  330 
  331    /* Separate current directory/pattern level from any possible remaining
  332     * pattern in order to be able to use it for fnmatch(3) */
  333    if((ccp = memchr(sgcp->sgc_patdat, '/', sgcp->sgc_patlen)) == NULL)
  334       nsgc.sgc_patlen = 0;
  335    else{
  336       nsgc = *sgcp;
  337       nsgc.sgc_flags |= a_DEEP;
  338       sgcp->sgc_patlen = PTR2SIZE((nsgc.sgc_patdat = &ccp[1]) -
  339             &sgcp->sgc_patdat[0]);
  340       nsgc.sgc_patlen -= sgcp->sgc_patlen;
  341 
  342       /* Trim solidus, everywhere */
  343       if(sgcp->sgc_patlen > 0){
  344          assert(sgcp->sgc_patdat[sgcp->sgc_patlen -1] == '/');
  345          ((char*)n_UNCONST(sgcp->sgc_patdat))[--sgcp->sgc_patlen] = '\0';
  346       }
  347       while(nsgc.sgc_patlen > 0 && nsgc.sgc_patdat[0] == '/'){
  348          --nsgc.sgc_patlen;
  349          ++nsgc.sgc_patdat;
  350       }
  351    }
  352 
  353    /* Our current directory level */
  354    /* xxx Plenty of room for optimizations, like quickshot lstat(2) which may
  355     * xxx be the (sole) result depending on pattern surroundings, etc. */
  356    if((dp = opendir(myp)) == NULL){
  357       int err;
  358 
  359       switch((err = n_err_no)){
  360       case n_ERR_NOTDIR:
  361          ccp = N_("cannot access paths under non-directory");
  362          goto jerr;
  363       case n_ERR_NOENT:
  364          ccp = N_("path component of (sub)pattern non-existent");
  365          goto jerr;
  366       case n_ERR_ACCES:
  367          ccp = N_("file permission for file (sub)pattern denied");
  368          goto jerr;
  369       case n_ERR_NFILE:
  370       case n_ERR_MFILE:
  371          ccp = N_("file descriptor limit reached, cannot open directory");
  372          goto jerr;
  373       default:
  374          ccp = N_("cannot open path component as directory");
  375          goto jerr;
  376       }
  377    }
  378 
  379    /* As necessary, quote bytes in the current pattern */
  380    /* C99 */{
  381       char *ncp;
  382       size_t i;
  383       bool_t need;
  384 
  385       for(need = FAL0, i = 0, myp = sgcp->sgc_patdat; *myp != '\0'; ++myp)
  386          switch(*myp){
  387          case '\'': case '"': case '\\': case '$':
  388          case ' ': case '\t':
  389             need = TRU1;
  390             ++i;
  391             /* FALLTHRU */
  392          default:
  393             ++i;
  394             break;
  395          }
  396 
  397       if(need){
  398          ncp = salloc(i +1);
  399          for(i = 0, myp = sgcp->sgc_patdat; *myp != '\0'; ++myp)
  400             switch(*myp){
  401             case '\'': case '"': case '\\': case '$':
  402             case ' ': case '\t':
  403                ncp[i++] = '\\';
  404                /* FALLTHRU */
  405             default:
  406                ncp[i++] = *myp;
  407                break;
  408             }
  409          ncp[i] = '\0';
  410          myp = ncp;
  411       }else
  412          myp = sgcp->sgc_patdat;
  413    }
  414 
  415    while((dep = readdir(dp)) != NULL){
  416       switch(fnmatch(myp, dep->d_name, FNM_PATHNAME | FNM_PERIOD)){
  417       case 0:{
  418          /* A match expresses the desire to recurse if there is more pattern */
  419          if(nsgc.sgc_patlen > 0){
  420             bool_t isdir;
  421 
  422             n_string_push_cp((sgcp->sgc_outer->s_len > 1
  423                   ? n_string_push_c(sgcp->sgc_outer, '/') : sgcp->sgc_outer),
  424                dep->d_name);
  425 
  426             isdir = FAL0;
  427 #ifdef HAVE_DIRENT_TYPE
  428             if(dep->d_type == DT_DIR)
  429                isdir = TRU1;
  430             else if(dep->d_type == DT_LNK || dep->d_type == DT_UNKNOWN)
  431 #endif
  432             {
  433                struct stat sb;
  434 
  435                if(stat(n_string_cp(sgcp->sgc_outer), &sb)){
  436                   ccp = N_("I/O error when querying file status");
  437                   goto jerr;
  438                }else if(S_ISDIR(sb.st_mode))
  439                   isdir = TRU1;
  440             }
  441 
  442             /* TODO We recurse with current dir FD open, which could E[MN]FILE!
  443              * TODO Instead save away a list of such n_string's for later */
  444             if(isdir && !a_shexp__glob(&nsgc, slpp)){
  445                ccp = (char*)1;
  446                goto jleave;
  447             }
  448 
  449             n_string_trunc(sgcp->sgc_outer, old_outerlen);
  450          }else{
  451             struct n_strlist *slp;
  452             size_t i, j;
  453 
  454             i = strlen(dep->d_name);
  455             j = (old_outerlen > 0) ? old_outerlen + 1 + i : i;
  456             slp = n_STRLIST_ALLOC(j);
  457             *slpp = slp;
  458             slpp = &slp->sl_next;
  459             slp->sl_next = NULL;
  460             if((j = old_outerlen) > 0){
  461                memcpy(&slp->sl_dat[0], sgcp->sgc_outer->s_dat, j);
  462                if(slp->sl_dat[j -1] != '/')
  463                   slp->sl_dat[j++] = '/';
  464             }
  465             memcpy(&slp->sl_dat[j], dep->d_name, i);
  466             slp->sl_dat[j += i] = '\0';
  467             slp->sl_len = j;
  468          }
  469          }break;
  470       case FNM_NOMATCH:
  471          break;
  472       default:
  473          ccp = N_("fnmatch(3) cannot handle file (sub)pattern");
  474          goto jerr;
  475       }
  476    }
  477 
  478    ccp = NULL;
  479 jleave:
  480    if(dp != NULL)
  481       closedir(dp);
  482    NYD2_LEAVE;
  483    return (ccp == NULL);
  484 
  485 jerr:
  486    if(!(sgcp->sgc_flags & a_SILENT)){
  487       char const *s2, *s3;
  488 
  489       if(sgcp->sgc_outer->s_len > 0){
  490          s2 = n_shexp_quote_cp(n_string_cp(sgcp->sgc_outer), FAL0);
  491          s3 = "/";
  492       }else
  493          s2 = s3 = n_empty;
  494 
  495       n_err("%s: %s%s%s\n", V_(ccp), s2, s3,
  496          n_shexp_quote_cp(sgcp->sgc_patdat, FAL0));
  497    }
  498    goto jleave;
  499 }
  500 
  501 static int
  502 a_shexp__globsort(void const *cvpa, void const *cvpb){
  503    int rv;
  504    struct n_strlist const * const *slpa, * const *slpb;
  505    NYD2_ENTER;
  506 
  507    slpa = cvpa;
  508    slpb = cvpb;
  509    rv = asccasecmp((*slpa)->sl_dat, (*slpb)->sl_dat);
  510    NYD2_LEAVE;
  511    return rv;
  512 }
  513 #endif /* HAVE_FNMATCH */
  514 
  515 static void
  516 a_shexp__quote(struct a_shexp_quote_ctx *sqcp, struct a_shexp_quote_lvl *sqlp){
  517    /* XXX Because of the problems caused by ISO C multibyte interface we cannot
  518     * XXX use the recursive implementation because of stateful encodings.
  519     * XXX I.e., if a quoted substring cannot be self-contained - the data after
  520     * XXX the quote relies on "the former state", then this doesn't make sense.
  521     * XXX Therefore this is not fully programmed out but instead only detects
  522     * XXX the "most fancy" quoting necessary, and directly does that.
  523     * XXX As a result of this, T_REVSOL and T_DOUBLE are not even considered.
  524     * XXX Otherwise we rather have to convert to wide first and act on that,
  525     * XXX e.g., call visual_info(n_VISUAL_INFO_WOUT_CREATE) on entire input */
  526 #undef a_SHEXP_QUOTE_RECURSE /* XXX (Needs complete revisit, then) */
  527 #ifdef a_SHEXP_QUOTE_RECURSE
  528 # define jrecurse jrecurse
  529    struct a_shexp_quote_lvl sql;
  530 #else
  531 # define jrecurse jstep
  532 #endif
  533    struct n_visual_info_ctx vic;
  534    union {struct a_shexp_quote_lvl *head; struct n_string *store;} u;
  535    ui32_t flags;
  536    size_t il;
  537    char const *ib, *ib_base;
  538    NYD2_ENTER;
  539 
  540    ib_base = ib = sqlp->sql_dat.s;
  541    il = sqlp->sql_dat.l;
  542    flags = sqlp->sql_flags;
  543 
  544    /* Iterate over the entire input, classify characters and type of quotes
  545     * along the way.  Whenever a quote change has to be applied, adjust flags
  546     * for the new situation -, setup sql.* and recurse- */
  547    while(il > 0){
  548       char c;
  549 
  550       c = *ib;
  551       if(cntrlchar(c)){
  552          if(flags & a_SHEXP_QUOTE_T_DOLLAR)
  553             goto jstep;
  554          if(c == '\t' && (flags & (a_SHEXP_QUOTE_T_REVSOL |
  555                a_SHEXP_QUOTE_T_SINGLE | a_SHEXP_QUOTE_T_DOUBLE)))
  556             goto jstep;
  557 #ifdef a_SHEXP_QUOTE_RECURSE
  558          ++sqcp->sqc_cnt_dollar;
  559 #endif
  560          flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_DOLLAR;
  561          goto jrecurse;
  562       }else if(blankspacechar(c) || c == '|' || c == '&' || c == ';' ||
  563             /* Whereas we don't support those, quote them for the sh(1)ell */
  564             c == '(' || c == ')' || c == '<' || c == '>' ||
  565             c == '"' || c == '$'){
  566          if(flags & a_SHEXP_QUOTE_T_MASK)
  567             goto jstep;
  568 #ifdef a_SHEXP_QUOTE_RECURSE
  569          ++sqcp->sqc_cnt_single;
  570 #endif
  571          flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_SINGLE;
  572          goto jrecurse;
  573       }else if(c == '\''){
  574          if(flags & (a_SHEXP_QUOTE_T_MASK & ~a_SHEXP_QUOTE_T_SINGLE))
  575             goto jstep;
  576 #ifdef a_SHEXP_QUOTE_RECURSE
  577          ++sqcp->sqc_cnt_dollar;
  578 #endif
  579          flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_DOLLAR;
  580          goto jrecurse;
  581       }else if(c == '\\' || (c == '#' && ib == ib_base)){
  582          if(flags & a_SHEXP_QUOTE_T_MASK)
  583             goto jstep;
  584 #ifdef a_SHEXP_QUOTE_RECURSE
  585          ++sqcp->sqc_cnt_single;
  586 #endif
  587          flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_SINGLE;
  588          goto jrecurse;
  589       }else if(!asciichar(c)){
  590          /* Need to keep together multibytes */
  591 #ifdef a_SHEXP_QUOTE_RECURSE
  592          memset(&vic, 0, sizeof vic);
  593          vic.vic_indat = ib;
  594          vic.vic_inlen = il;
  595          n_visual_info(&vic,
  596             n_VISUAL_INFO_ONE_CHAR | n_VISUAL_INFO_SKIP_ERRORS);
  597 #endif
  598          /* xxx check whether resulting \u would be ASCII */
  599          if(!(flags & a_SHEXP_QUOTE_ROUNDTRIP) ||
  600                (flags & a_SHEXP_QUOTE_T_DOLLAR)){
  601 #ifdef a_SHEXP_QUOTE_RECURSE
  602             ib = vic.vic_oudat;
  603             il = vic.vic_oulen;
  604             continue;
  605 #else
  606             goto jstep;
  607 #endif
  608          }
  609 #ifdef a_SHEXP_QUOTE_RECURSE
  610          ++sqcp->sqc_cnt_dollar;
  611 #endif
  612          flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_DOLLAR;
  613          goto jrecurse;
  614       }else
  615 jstep:
  616          ++ib, --il;
  617    }
  618    sqlp->sql_flags = flags;
  619 
  620    /* Level made the great and completed processing input.  Reverse the list of
  621     * levels, detect the "most fancy" quote type needed along this way */
  622    /* XXX Due to restriction as above very crude */
  623    for(flags = 0, il = 0, u.head = NULL; sqlp != NULL;){
  624       struct a_shexp_quote_lvl *tmp;
  625 
  626       tmp = sqlp->sql_link;
  627       sqlp->sql_link = u.head;
  628       u.head = sqlp;
  629       il += sqlp->sql_dat.l;
  630       if(sqlp->sql_flags & a_SHEXP_QUOTE_T_MASK)
  631          il += (sqlp->sql_dat.l >> 1);
  632       flags |= sqlp->sql_flags;
  633       sqlp = tmp;
  634    }
  635    sqlp = u.head;
  636 
  637    /* Finally work the substrings in the correct order, adjusting quotes along
  638     * the way as necessary.  Start off with the "most fancy" quote, so that
  639     * the user sees an overall boundary she can orientate herself on.
  640     * We do it like that to be able to give the user some "encapsulation
  641     * experience", to address what strikes me is a problem of sh(1)ell quoting:
  642     * different to, e.g., perl(1), where you see at a glance where a string
  643     * starts and ends, sh(1) quoting occurs at the "top level", disrupting the
  644     * visual appearance of "a string" as such */
  645    u.store = n_string_reserve(sqcp->sqc_store, il);
  646 
  647    if(flags & a_SHEXP_QUOTE_T_DOLLAR){
  648       u.store = n_string_push_buf(u.store, "$'", sizeof("$'") -1);
  649       flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_DOLLAR;
  650    }else if(flags & a_SHEXP_QUOTE_T_DOUBLE){
  651       u.store = n_string_push_c(u.store, '"');
  652       flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_DOUBLE;
  653    }else if(flags & a_SHEXP_QUOTE_T_SINGLE){
  654       u.store = n_string_push_c(u.store, '\'');
  655       flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_SINGLE;
  656    }else /*if(flags & a_SHEXP_QUOTE_T_REVSOL)*/
  657       flags &= ~a_SHEXP_QUOTE_T_MASK;
  658 
  659    /* Work all the levels */
  660    for(; sqlp != NULL; sqlp = sqlp->sql_link){
  661       /* As necessary update our mode of quoting */
  662 #ifdef a_SHEXP_QUOTE_RECURSE
  663       il = 0;
  664 
  665       switch(sqlp->sql_flags & a_SHEXP_QUOTE_T_MASK){
  666       case a_SHEXP_QUOTE_T_DOLLAR:
  667          if(!(flags & a_SHEXP_QUOTE_T_DOLLAR))
  668             il = a_SHEXP_QUOTE_T_DOLLAR;
  669          break;
  670       case a_SHEXP_QUOTE_T_DOUBLE:
  671          if(!(flags & (a_SHEXP_QUOTE_T_DOUBLE | a_SHEXP_QUOTE_T_DOLLAR)))
  672             il = a_SHEXP_QUOTE_T_DOLLAR;
  673          break;
  674       case a_SHEXP_QUOTE_T_SINGLE:
  675          if(!(flags & (a_SHEXP_QUOTE_T_REVSOL | a_SHEXP_QUOTE_T_SINGLE |
  676                a_SHEXP_QUOTE_T_DOUBLE | a_SHEXP_QUOTE_T_DOLLAR)))
  677             il = a_SHEXP_QUOTE_T_SINGLE;
  678          break;
  679       default:
  680       case a_SHEXP_QUOTE_T_REVSOL:
  681          if(!(flags & (a_SHEXP_QUOTE_T_REVSOL | a_SHEXP_QUOTE_T_SINGLE |
  682                a_SHEXP_QUOTE_T_DOUBLE | a_SHEXP_QUOTE_T_DOLLAR)))
  683             il = a_SHEXP_QUOTE_T_REVSOL;
  684          break;
  685       }
  686 
  687       if(il != 0){
  688          if(flags & (a_SHEXP_QUOTE_T_SINGLE | a_SHEXP_QUOTE_T_DOLLAR))
  689             u.store = n_string_push_c(u.store, '\'');
  690          else if(flags & a_SHEXP_QUOTE_T_DOUBLE)
  691             u.store = n_string_push_c(u.store, '"');
  692          flags &= ~a_SHEXP_QUOTE_T_MASK;
  693 
  694          flags |= (ui32_t)il;
  695          if(flags & a_SHEXP_QUOTE_T_DOLLAR)
  696             u.store = n_string_push_buf(u.store, "$'", sizeof("$'") -1);
  697          else if(flags & a_SHEXP_QUOTE_T_DOUBLE)
  698             u.store = n_string_push_c(u.store, '"');
  699          else if(flags & a_SHEXP_QUOTE_T_SINGLE)
  700             u.store = n_string_push_c(u.store, '\'');
  701       }
  702 #endif /* a_SHEXP_QUOTE_RECURSE */
  703 
  704       /* Work the level's substring */
  705       ib = sqlp->sql_dat.s;
  706       il = sqlp->sql_dat.l;
  707 
  708       while(il > 0){
  709          char c2, c;
  710 
  711          c = *ib;
  712 
  713          if(cntrlchar(c)){
  714             assert(c == '\t' || (flags & a_SHEXP_QUOTE_T_DOLLAR));
  715             assert((flags & (a_SHEXP_QUOTE_T_REVSOL | a_SHEXP_QUOTE_T_SINGLE |
  716                a_SHEXP_QUOTE_T_DOUBLE | a_SHEXP_QUOTE_T_DOLLAR)));
  717             switch((c2 = c)){
  718             case 0x07: c = 'a'; break;
  719             case 0x08: c = 'b'; break;
  720             case 0x0A: c = 'n'; break;
  721             case 0x0B: c = 'v'; break;
  722             case 0x0C: c = 'f'; break;
  723             case 0x0D: c = 'r'; break;
  724             case 0x1B: c = 'E'; break;
  725             default: break;
  726             case 0x09:
  727                if(flags & a_SHEXP_QUOTE_T_DOLLAR){
  728                   c = 't';
  729                   break;
  730                }
  731                if(flags & a_SHEXP_QUOTE_T_REVSOL)
  732                   u.store = n_string_push_c(u.store, '\\');
  733                goto jpush;
  734             }
  735             u.store = n_string_push_c(u.store, '\\');
  736             if(c == c2){
  737                u.store = n_string_push_c(u.store, 'c');
  738                c ^= 0x40;
  739             }
  740             goto jpush;
  741          }else if(blankspacechar(c) || c == '|' || c == '&' || c == ';' ||
  742                /* Whereas we don't support those, quote them for the sh(1)ell */
  743                c == '(' || c == ')' || c == '<' || c == '>' ||
  744                c == '"' || c == '$'){
  745             if(flags & (a_SHEXP_QUOTE_T_SINGLE | a_SHEXP_QUOTE_T_DOLLAR))
  746                goto jpush;
  747             assert(flags & (a_SHEXP_QUOTE_T_REVSOL | a_SHEXP_QUOTE_T_DOUBLE));
  748             u.store = n_string_push_c(u.store, '\\');
  749             goto jpush;
  750          }else if(c == '\''){
  751             if(flags & a_SHEXP_QUOTE_T_DOUBLE)
  752                goto jpush;
  753             assert(!(flags & a_SHEXP_QUOTE_T_SINGLE));
  754             u.store = n_string_push_c(u.store, '\\');
  755             goto jpush;
  756          }else if(c == '\\' || (c == '#' && ib == ib_base)){
  757             if(flags & a_SHEXP_QUOTE_T_SINGLE)
  758                goto jpush;
  759             assert(flags & (a_SHEXP_QUOTE_T_REVSOL | a_SHEXP_QUOTE_T_DOUBLE |
  760                a_SHEXP_QUOTE_T_DOLLAR));
  761             u.store = n_string_push_c(u.store, '\\');
  762             goto jpush;
  763          }else if(asciichar(c)){
  764             /* Shorthand: we can simply push that thing out */
  765 jpush:
  766             u.store = n_string_push_c(u.store, c);
  767             ++ib, --il;
  768          }else{
  769             /* Not an ASCII character, take care not to split up multibyte
  770              * sequences etc.  For the sake of compile testing, don't enwrap in
  771              * HAVE_ALWAYS_UNICODE_LOCALE || HAVE_NATCH_CHAR */
  772             if(n_psonce & n_PSO_UNICODE){
  773                ui32_t uc;
  774                char const *ib2;
  775                size_t il2, il3;
  776 
  777                ib2 = ib;
  778                il2 = il;
  779                if((uc = n_utf8_to_utf32(&ib2, &il2)) != UI32_MAX){
  780                   char itoa[32];
  781                   char const *cp;
  782 
  783                   il2 = PTR2SIZE(&ib2[0] - &ib[0]);
  784                   if((flags & a_SHEXP_QUOTE_ROUNDTRIP) || uc == 0xFFFDu){
  785                      /* Use padding to make ambiguities impossible */
  786                      il3 = snprintf(itoa, sizeof itoa, "\\%c%0*X",
  787                            (uc > 0xFFFFu ? 'U' : 'u'),
  788                            (int)(uc > 0xFFFFu ? 8 : 4), uc);
  789                      cp = itoa;
  790                   }else{
  791                      il3 = il2;
  792                      cp = &ib[0];
  793                   }
  794                   u.store = n_string_push_buf(u.store, cp, il3);
  795                   ib += il2, il -= il2;
  796                   continue;
  797                }
  798             }
  799 
  800             memset(&vic, 0, sizeof vic);
  801             vic.vic_indat = ib;
  802             vic.vic_inlen = il;
  803             n_visual_info(&vic,
  804                n_VISUAL_INFO_ONE_CHAR | n_VISUAL_INFO_SKIP_ERRORS);
  805 
  806             /* Work this substring as sensitive as possible */
  807             il -= vic.vic_oulen;
  808             if(!(flags & a_SHEXP_QUOTE_ROUNDTRIP))
  809                u.store = n_string_push_buf(u.store, ib, il);
  810 #ifdef HAVE_ICONV
  811             else if((vic.vic_indat = n_iconv_onetime_cp(n_ICONV_NONE,
  812                   "utf-8", ok_vlook(ttycharset), savestrbuf(ib, il))) != NULL){
  813                ui32_t uc;
  814                char const *ib2;
  815                size_t il2, il3;
  816 
  817                il2 = strlen(ib2 = vic.vic_indat);
  818                if((uc = n_utf8_to_utf32(&ib2, &il2)) != UI32_MAX){
  819                   char itoa[32];
  820 
  821                   il2 = PTR2SIZE(&ib2[0] - &vic.vic_indat[0]);
  822                   /* Use padding to make ambiguities impossible */
  823                   il3 = snprintf(itoa, sizeof itoa, "\\%c%0*X",
  824                         (uc > 0xFFFFu ? 'U' : 'u'),
  825                         (int)(uc > 0xFFFFu ? 8 : 4), uc);
  826                   u.store = n_string_push_buf(u.store, itoa, il3);
  827                }else
  828                   goto Jxseq;
  829             }
  830 #endif
  831             else
  832 #ifdef HAVE_ICONV
  833                  Jxseq:
  834 #endif
  835                         while(il-- > 0){
  836                u.store = n_string_push_buf(u.store, "\\xFF",
  837                      sizeof("\\xFF") -1);
  838                n_c_to_hex_base16(&u.store->s_dat[u.store->s_len - 2], *ib++);
  839             }
  840 
  841             ib = vic.vic_oudat;
  842             il = vic.vic_oulen;
  843          }
  844       }
  845    }
  846 
  847    /* Close an open quote */
  848    if(flags & (a_SHEXP_QUOTE_T_SINGLE | a_SHEXP_QUOTE_T_DOLLAR))
  849       u.store = n_string_push_c(u.store, '\'');
  850    else if(flags & a_SHEXP_QUOTE_T_DOUBLE)
  851       u.store = n_string_push_c(u.store, '"');
  852 #ifdef a_SHEXP_QUOTE_RECURSE
  853 jleave:
  854 #endif
  855    NYD2_LEAVE;
  856    return;
  857 
  858 #ifdef a_SHEXP_QUOTE_RECURSE
  859 jrecurse:
  860    sqlp->sql_dat.l -= il;
  861 
  862    sql.sql_link = sqlp;
  863    sql.sql_dat.s = n_UNCONST(ib);
  864    sql.sql_dat.l = il;
  865    sql.sql_flags = flags;
  866    a_shexp__quote(sqcp, &sql);
  867    goto jleave;
  868 #endif
  869 
  870 #undef jrecurse
  871 #undef a_SHEXP_QUOTE_RECURSE
  872 }
  873 
  874 FL char *
  875 fexpand(char const *name, enum fexp_mode fexpm) /* TODO in parts: -> URL::!! */
  876 {
  877    struct str proto, s;
  878    char const *res, *cp;
  879    bool_t dyn, haveproto;
  880    NYD_ENTER;
  881 
  882    n_pstate &= ~n_PS_EXPAND_MULTIRESULT;
  883    dyn = FAL0;
  884 
  885    /* The order of evaluation is "%" and "#" expand into constants.
  886     * "&" can expand into "+".  "+" can expand into shell meta characters.
  887     * Shell meta characters expand into constants.
  888     * This way, we make no recursive expansion */
  889    if((fexpm & FEXP_NSHORTCUT) || (res = shortcut_expand(name)) == NULL)
  890       res = n_UNCONST(name);
  891 
  892 jprotonext:
  893    n_UNINIT(proto.s, NULL), n_UNINIT(proto.l, 0);
  894    haveproto = FAL0;
  895    for(cp = res; *cp && *cp != ':'; ++cp)
  896       if(!alnumchar(*cp))
  897          goto jnoproto;
  898    if(cp[0] == ':' && cp[1] == '/' && cp[2] == '/'){
  899       haveproto = TRU1;
  900       proto.s = n_UNCONST(res);
  901       cp += 3;
  902       proto.l = PTR2SIZE(cp - res);
  903       res = cp;
  904    }
  905 
  906 jnoproto:
  907    if(!(fexpm & FEXP_NSPECIAL)){
  908 jnext:
  909       dyn = FAL0;
  910       switch(*res){
  911       case '%':
  912          if(res[1] == ':' && res[2] != '\0'){
  913             res = &res[2];
  914             goto jprotonext;
  915          }else{
  916             bool_t force;
  917 
  918             force = (res[1] != '\0');
  919             res = a_shexp_findmail((force ? &res[1] : ok_vlook(LOGNAME)),
  920                   force);
  921             if(force)
  922                goto jislocal;
  923          }
  924          goto jnext;
  925       case '#':
  926          if (res[1] != '\0')
  927             break;
  928          if (prevfile[0] == '\0') {
  929             n_err(_("No previous file\n"));
  930             res = NULL;
  931             goto jleave;
  932          }
  933          res = prevfile;
  934          goto jislocal;
  935       case '&':
  936          if (res[1] == '\0')
  937             res = ok_vlook(MBOX);
  938          break;
  939       default:
  940          break;
  941       }
  942    }
  943 
  944 #ifdef HAVE_IMAP
  945    if(res[0] == '@' && which_protocol(mailname, FAL0, FAL0, NULL)
  946          == PROTO_IMAP){
  947       res = str_concat_csvl(&s, protbase(mailname), "/", &res[1], NULL)->s;
  948       dyn = TRU1;
  949    }
  950 #endif
  951 
  952    /* POSIX: if *folder* unset or null, "+" shall be retained */
  953    if(!(fexpm & FEXP_NFOLDER) && *res == '+' &&
  954          *(cp = n_folder_query()) != '\0'){
  955       res = str_concat_csvl(&s, cp, &res[1], NULL)->s;
  956       dyn = TRU1;
  957    }
  958 
  959    /* Do some meta expansions */
  960    if((fexpm & (FEXP_NSHELL | FEXP_NVAR)) != FEXP_NVAR &&
  961          ((fexpm & FEXP_NSHELL) ? (strchr(res, '$') != NULL)
  962           : n_anyof_cp("{}[]*?$", res))){
  963       bool_t doexp;
  964 
  965       if(fexpm & FEXP_NOPROTO)
  966          doexp = TRU1;
  967       else{
  968          cp = haveproto ? savecat(savestrbuf(proto.s, proto.l), res) : res;
  969 
  970          switch(which_protocol(cp, TRU1, FAL0, NULL)){
  971          case PROTO_FILE:
  972          case PROTO_MAILDIR:
  973             doexp = TRU1;
  974             break;
  975          default:
  976             doexp = FAL0;
  977             break;
  978          }
  979       }
  980 
  981       if(doexp){
  982          struct str shin;
  983          struct n_string shou, *shoup;
  984 
  985          shin.s = n_UNCONST(res);
  986          shin.l = UIZ_MAX;
  987          shoup = n_string_creat_auto(&shou);
  988          for(;;){
  989             enum n_shexp_state shs;
  990 
  991             /* TODO shexp: take care to not include backtick eval once avail! */
  992             shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG_D_V |
  993                   n_SHEXP_PARSE_QUOTE_AUTO_FIXED | n_SHEXP_PARSE_QUOTE_AUTO_DQ |
  994                   n_SHEXP_PARSE_QUOTE_AUTO_CLOSE), shoup, &shin, NULL);
  995             if(shs & n_SHEXP_STATE_STOP)
  996                break;
  997          }
  998          res = n_string_cp(shoup);
  999          /*shoup = n_string_drop_ownership(shoup);*/
 1000          dyn = TRU1;
 1001 
 1002          if(res[0] == '~')
 1003             res = a_shexp_tilde(res);
 1004 
 1005          if(!(fexpm & FEXP_NSHELL) &&
 1006                (res = a_shexp_globname(res, fexpm)) == NULL)
 1007             goto jleave;
 1008          dyn = TRU1;
 1009       }/* else no tilde */
 1010    }else if(res[0] == '~'){
 1011       res = a_shexp_tilde(res);
 1012       dyn = TRU1;
 1013    }
 1014 
 1015 jislocal:
 1016    if(res != NULL && haveproto){
 1017       res = savecat(savestrbuf(proto.s, proto.l), res);
 1018       dyn = TRU1;
 1019    }
 1020 
 1021    if(fexpm & FEXP_LOCAL){
 1022       switch (which_protocol(res, FAL0, FAL0, NULL)) {
 1023       case PROTO_FILE:
 1024       case PROTO_MAILDIR: /* Cannot happen since we don't stat(2), but.. */
 1025          break;
 1026       default:
 1027          n_err(_("Not a local file or directory: %s\n"),
 1028             n_shexp_quote_cp(name, FAL0));
 1029          res = NULL;
 1030          break;
 1031       }
 1032    }
 1033 
 1034 jleave:
 1035    if(res != NULL && !dyn)
 1036       res = savestr(res);
 1037    NYD_LEAVE;
 1038    return n_UNCONST(res);
 1039 }
 1040 
 1041 FL enum n_shexp_state
 1042 n_shexp_parse_token(enum n_shexp_parse_flags flags, struct n_string *store,
 1043       struct str *input, void const **cookie){
 1044    /* TODO shexp_parse_token: WCHAR */
 1045    ui32_t last_known_meta_trim_len;
 1046    char c2, c, quotec, utf[8];
 1047    enum n_shexp_state rv;
 1048    size_t i, il;
 1049    char const *ifs, *ifs_ws, *ib_save, *ib;
 1050    enum{
 1051       a_NONE = 0,
 1052       a_SKIPQ = 1u<<0,     /* Skip rest of this quote (\u0 ..) */
 1053       a_SKIPT = 1u<<1,     /* Skip entire token (\c@) */
 1054       a_SKIPMASK = a_SKIPQ | a_SKIPT,
 1055       a_SURPLUS = 1u<<2,   /* Extended sequence interpretation */
 1056       a_NTOKEN = 1u<<3,    /* "New token": e.g., comments are possible */
 1057       a_BRACE = 1u<<4,     /* Variable substitution: brace enclosed */
 1058       a_DIGIT1 = 1u<<5,    /* ..first character was digit */
 1059       a_NONDIGIT = 1u<<6,  /* ..has seen any non-digits */
 1060       a_VARSUBST_MASK = n_BITENUM_MASK(4, 6),
 1061 
 1062       a_ROUND_MASK = a_SKIPT | (int)~n_BITENUM_MASK(0, 7),
 1063       a_COOKIE = 1u<<8,
 1064       a_EXPLODE = 1u<<9,
 1065       a_CONSUME = 1u<<10,  /* When done, "consume" remaining input */
 1066       a_TMP = 1u<<30
 1067    } state;
 1068    NYD2_ENTER;
 1069 
 1070    assert((flags & n_SHEXP_PARSE_DRYRUN) || store != NULL);
 1071    assert(input != NULL);
 1072    assert(input->l == 0 || input->s != NULL);
 1073    assert(!(flags & n_SHEXP_PARSE_LOG) || !(flags & n_SHEXP_PARSE_LOG_D_V));
 1074    assert(!(flags & n_SHEXP_PARSE_IFS_ADD_COMMA) ||
 1075       !(flags & n_SHEXP_PARSE_IFS_IS_COMMA));
 1076    assert(!(flags & n_SHEXP_PARSE_QUOTE_AUTO_FIXED) ||
 1077       (flags & n__SHEXP_PARSE_QUOTE_AUTO_MASK));
 1078 
 1079    if((flags & n_SHEXP_PARSE_LOG_D_V) && (n_poption & n_PO_D_V))
 1080       flags |= n_SHEXP_PARSE_LOG;
 1081    if(flags & n_SHEXP_PARSE_QUOTE_AUTO_FIXED)
 1082       flags |= n_SHEXP_PARSE_QUOTE_AUTO_CLOSE;
 1083 
 1084    if((flags & n_SHEXP_PARSE_TRUNC) && store != NULL)
 1085       store = n_string_trunc(store, 0);
 1086 
 1087    if(flags & (n_SHEXP_PARSE_IFS_VAR | n_SHEXP_PARSE_TRIM_IFSSPACE)){
 1088       ifs = ok_vlook(ifs);
 1089       ifs_ws = ok_vlook(ifs_ws);
 1090    }else{
 1091       n_UNINIT(ifs, n_empty);
 1092       n_UNINIT(ifs_ws, n_empty);
 1093    }
 1094 
 1095    state = a_NONE;
 1096    ib = input->s;
 1097    if((il = input->l) == UIZ_MAX)
 1098       input->l = il = strlen(ib);
 1099    n_UNINIT(c, '\0');
 1100 
 1101    if(cookie != NULL && *cookie != NULL){
 1102       assert(!(flags & n_SHEXP_PARSE_DRYRUN));
 1103       state |= a_COOKIE;
 1104    }
 1105 
 1106    rv = n_SHEXP_STATE_NONE;
 1107 jrestart_empty:
 1108    rv &= n_SHEXP_STATE_WS_LEAD;
 1109    state &= a_ROUND_MASK;
 1110 
 1111    /* In cookie mode, the next ARGV entry is the token already, unchanged,
 1112     * since it has already been expanded before! */
 1113    if(state & a_COOKIE){
 1114       char const * const *xcookie, *cp;
 1115 
 1116       i = store->s_len;
 1117       xcookie = *cookie;
 1118       if((store = n_string_push_cp(store, *xcookie))->s_len > 0)
 1119          rv |= n_SHEXP_STATE_OUTPUT;
 1120       if(*++xcookie == NULL){
 1121          *cookie = NULL;
 1122          state &= ~a_COOKIE;
 1123          flags |= n_SHEXP_PARSE_QUOTE_AUTO_DQ; /* ..why we are here! */
 1124       }else
 1125          *cookie = n_UNCONST(xcookie);
 1126 
 1127       for(cp = &n_string_cp(store)[i]; (c = *cp++) != '\0';)
 1128          if(cntrlchar(c)){
 1129             rv |= n_SHEXP_STATE_CONTROL;
 1130             break;
 1131          }
 1132 
 1133       /* The last exploded cookie will join with the yielded input token, so
 1134        * simply fall through in this case */
 1135       if(state & a_COOKIE)
 1136          goto jleave_quick;
 1137    }else{
 1138 jrestart:
 1139       if(flags & n_SHEXP_PARSE_TRIM_SPACE){
 1140          for(; il > 0; ++ib, --il){
 1141             if(!blankspacechar(*ib))
 1142                break;
 1143             rv |= n_SHEXP_STATE_WS_LEAD;
 1144          }
 1145       }
 1146 
 1147       if(flags & n_SHEXP_PARSE_TRIM_IFSSPACE){
 1148          for(; il > 0; ++ib, --il){
 1149             if(strchr(ifs_ws, *ib) == NULL)
 1150                break;
 1151             rv |= n_SHEXP_STATE_WS_LEAD;
 1152          }
 1153       }
 1154 
 1155       input->s = n_UNCONST(ib);
 1156       input->l = il;
 1157    }
 1158 
 1159    if(il == 0){
 1160       rv |= n_SHEXP_STATE_STOP;
 1161       goto jleave;
 1162    }
 1163 
 1164    if(store != NULL)
 1165       store = n_string_reserve(store, n_MIN(il, 32)); /* XXX */
 1166 
 1167    switch(flags & n__SHEXP_PARSE_QUOTE_AUTO_MASK){
 1168    case n_SHEXP_PARSE_QUOTE_AUTO_SQ:
 1169       quotec = '\'';
 1170       rv |= n_SHEXP_STATE_QUOTE;
 1171       break;
 1172    case n_SHEXP_PARSE_QUOTE_AUTO_DQ:
 1173       quotec = '"';
 1174       if(0){
 1175    case n_SHEXP_PARSE_QUOTE_AUTO_DSQ:
 1176          quotec = '\'';
 1177       }
 1178       rv |= n_SHEXP_STATE_QUOTE;
 1179       state |= a_SURPLUS;
 1180       break;
 1181    default:
 1182       quotec = '\0';
 1183       state |= a_NTOKEN;
 1184       break;
 1185    }
 1186 
 1187    /* TODO n_SHEXP_PARSE_META_SEMICOLON++, well, hack: we are not the shell,
 1188     * TODO we are not a language, and therefore the general *ifs-ws* and normal
 1189     * TODO whitespace trimming that input lines undergo (in a_go_evaluate())
 1190     * TODO has already happened, our result will be used *as is*, and therefore
 1191     * TODO we need to be aware of and remove trailing unquoted WS that would
 1192     * TODO otherwise remain, after we have seen a semicolon sequencer.
 1193     * By sheer luck we only need to track this in non-quote-mode */
 1194    last_known_meta_trim_len = UI32_MAX;
 1195 
 1196    while(il > 0){
 1197       --il, c = *ib++;
 1198 
 1199       /* If no quote-mode active.. */
 1200       if(quotec == '\0'){
 1201          if(c == '"' || c == '\''){
 1202             quotec = c;
 1203             if(c == '"')
 1204                state |= a_SURPLUS;
 1205             else
 1206                state &= ~a_SURPLUS;
 1207             state &= ~a_NTOKEN;
 1208             last_known_meta_trim_len = UI32_MAX;
 1209             rv |= n_SHEXP_STATE_QUOTE;
 1210             continue;
 1211          }else if(c == '$'){
 1212             if(il > 0){
 1213                state &= ~a_NTOKEN;
 1214                last_known_meta_trim_len = UI32_MAX;
 1215                if(*ib == '\''){
 1216                   --il, ++ib;
 1217                   quotec = '\'';
 1218                   state |= a_SURPLUS;
 1219                   rv |= n_SHEXP_STATE_QUOTE;
 1220                   continue;
 1221                }else
 1222                   goto J_var_expand;
 1223             }
 1224          }else if(c == '\\'){
 1225             /* Outside of quotes this just escapes any next character, but a sole
 1226              * <reverse solidus> at EOS is left unchanged */
 1227              if(il > 0)
 1228                --il, c = *ib++;
 1229             state &= ~a_NTOKEN;
 1230             last_known_meta_trim_len = UI32_MAX;
 1231          }
 1232          /* A comment may it be if no token has yet started */
 1233          else if(c == '#' && (state & a_NTOKEN)){
 1234             rv |= n_SHEXP_STATE_STOP;
 1235             /*last_known_meta_trim_len = UI32_MAX;*/
 1236             goto jleave;
 1237          }
 1238          /* Metacharacters which separate tokens must be turned on explicitly */
 1239          else if(c == '|' && (flags & n_SHEXP_PARSE_META_VERTBAR)){
 1240             rv |= n_SHEXP_STATE_META_VERTBAR;
 1241 
 1242             /* The parsed sequence may be _the_ output, so ensure we don't
 1243              * include the metacharacter, then. */
 1244             if(flags & (n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_META_KEEP))
 1245                ++il, --ib;
 1246             /*last_known_meta_trim_len = UI32_MAX;*/
 1247             break;
 1248          }else if(c == '&' && (flags & n_SHEXP_PARSE_META_AMPERSAND)){
 1249             rv |= n_SHEXP_STATE_META_AMPERSAND;
 1250 
 1251             /* The parsed sequence may be _the_ output, so ensure we don't
 1252              * include the metacharacter, then. */
 1253             if(flags & (n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_META_KEEP))
 1254                ++il, --ib;
 1255             /*last_known_meta_trim_len = UI32_MAX;*/
 1256             break;
 1257          }else if(c == ';' && (flags & n_SHEXP_PARSE_META_SEMICOLON)){
 1258             if(il > 0)
 1259                n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, ib, il);
 1260             rv |= n_SHEXP_STATE_META_SEMICOLON | n_SHEXP_STATE_STOP;
 1261             state |= a_CONSUME;
 1262             if(!(flags & n_SHEXP_PARSE_DRYRUN) && (rv & n_SHEXP_STATE_OUTPUT) &&
 1263                   last_known_meta_trim_len != UI32_MAX)
 1264                store = n_string_trunc(store, last_known_meta_trim_len);
 1265 
 1266             /* The parsed sequence may be _the_ output, so ensure we don't
 1267              * include the metacharacter, then. */
 1268             if(flags & (n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_META_KEEP))
 1269                ++il, --ib;
 1270             /*last_known_meta_trim_len = UI32_MAX;*/
 1271             break;
 1272          }else if(c == ',' && (flags &
 1273                (n_SHEXP_PARSE_IFS_ADD_COMMA | n_SHEXP_PARSE_IFS_IS_COMMA))){
 1274             /* The parsed sequence may be _the_ output, so ensure we don't
 1275              * include the metacharacter, then. */
 1276             if(flags & (n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_META_KEEP))
 1277                ++il, --ib;
 1278             /*last_known_meta_trim_len = UI32_MAX;*/
 1279             break;
 1280          }else{
 1281             ui8_t blnk;
 1282 
 1283             blnk = blankchar(c) ? 1 : 0;
 1284             blnk |= ((flags & (n_SHEXP_PARSE_IFS_VAR |
 1285                      n_SHEXP_PARSE_TRIM_IFSSPACE)) &&
 1286                   strchr(ifs_ws, c) != NULL) ? 2 : 0;
 1287 
 1288             if((!(flags & n_SHEXP_PARSE_IFS_VAR) && (blnk & 1)) ||
 1289                   ((flags & n_SHEXP_PARSE_IFS_VAR) &&
 1290                      ((blnk & 2) || strchr(ifs, c) != NULL))){
 1291                if(!(flags & n_SHEXP_PARSE_IFS_IS_COMMA)){
 1292                   /* The parsed sequence may be _the_ output, so ensure we don't
 1293                    * include the metacharacter, then. */
 1294                   if(flags & (n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_META_KEEP))
 1295                      ++il, --ib;
 1296                   /*last_known_meta_trim_len = UI32_MAX;*/
 1297                   break;
 1298                }
 1299                state |= a_NTOKEN;
 1300             }else
 1301                state &= ~a_NTOKEN;
 1302 
 1303             if(blnk && store != NULL){
 1304                if(last_known_meta_trim_len == UI32_MAX)
 1305                   last_known_meta_trim_len = store->s_len;
 1306             }else
 1307                last_known_meta_trim_len = UI32_MAX;
 1308          }
 1309       }else{
 1310          /* Quote-mode */
 1311          assert(!(state & a_NTOKEN));
 1312          if(c == quotec && !(flags & n_SHEXP_PARSE_QUOTE_AUTO_FIXED)){
 1313             state &= a_ROUND_MASK;
 1314             quotec = '\0';
 1315             /* Users may need to recognize the presence of empty quotes */
 1316             rv |= n_SHEXP_STATE_OUTPUT;
 1317             continue;
 1318          }else if(c == '\\' && (state & a_SURPLUS)){
 1319             ib_save = ib - 1;
 1320             /* A sole <reverse solidus> at EOS is treated as-is!  This is ok
 1321              * since the "closing quote" error will occur next, anyway */
 1322             if(il == 0)
 1323                ;
 1324             else if((c2 = *ib) == quotec){
 1325                --il, ++ib;
 1326                c = quotec;
 1327             }else if(quotec == '"'){
 1328                /* Double quotes, POSIX says:
 1329                 *    The <backslash> shall retain its special meaning as an
 1330                 *    escape character (see Section 2.2.1) only when followed
 1331                 *    by one of the following characters when considered
 1332                 *    special: $ ` " \ <newline> */
 1333                switch(c2){
 1334                case '$':
 1335                case '`':
 1336                /* case '"': already handled via c2 == quotec */
 1337                case '\\':
 1338                   --il, ++ib;
 1339                   c = c2;
 1340                   /* FALLTHRU */
 1341                default:
 1342                   break;
 1343                }
 1344             }else{
 1345                /* Dollar-single-quote */
 1346                --il, ++ib;
 1347                switch(c2){
 1348                case '"':
 1349                /* case '\'': already handled via c2 == quotec */
 1350                case '\\':
 1351                   c = c2;
 1352                   break;
 1353 
 1354                case 'b': c = '\b'; break;
 1355                case 'f': c = '\f'; break;
 1356                case 'n': c = '\n'; break;
 1357                case 'r': c = '\r'; break;
 1358                case 't': c = '\t'; break;
 1359                case 'v': c = '\v'; break;
 1360 
 1361                case 'E':
 1362                case 'e': c = '\033'; break;
 1363 
 1364                /* Control character */
 1365                case 'c':
 1366                   if(il == 0)
 1367                      goto j_dollar_ungetc;
 1368                   --il, c2 = *ib++;
 1369                   if(state & a_SKIPMASK)
 1370                      continue;
 1371                   /* ASCII C0: 0..1F, 7F <- @.._ (+ a-z -> A-Z), ? */
 1372                   c = upperconv(c2) ^ 0x40;
 1373                   if((ui8_t)c > 0x1F && c != 0x7F){
 1374                      if(flags & n_SHEXP_PARSE_LOG)
 1375                         n_err(_("Invalid \\c notation: %.*s: %.*s\n"),
 1376                            (int)input->l, input->s,
 1377                            (int)PTR2SIZE(ib - ib_save), ib_save);
 1378                      rv |= n_SHEXP_STATE_ERR_CONTROL;
 1379                   }
 1380                   /* As an implementation-defined extension, support \c@
 1381                    * EQ printf(1) alike \c */
 1382                   if(c == '\0'){
 1383                      state |= a_SKIPT;
 1384                      continue;
 1385                   }
 1386                   break;
 1387 
 1388                /* Octal sequence: 1 to 3 octal bytes */
 1389                case '0':
 1390                   /* As an extension (dependent on where you look, echo(1), or
 1391                    * awk(1)/tr(1) etc.), allow leading "0" octal indicator */
 1392                   if(il > 0 && (c = *ib) >= '0' && c <= '7'){
 1393                      c2 = c;
 1394                      --il, ++ib;
 1395                   }
 1396                   /* FALLTHRU */
 1397                case '1': case '2': case '3':
 1398                case '4': case '5': case '6': case '7':
 1399                   c2 -= '0';
 1400                   if(il > 0 && (c = *ib) >= '0' && c <= '7'){
 1401                      c2 = (c2 << 3) | (c - '0');
 1402                      --il, ++ib;
 1403                   }
 1404                   if(il > 0 && (c = *ib) >= '0' && c <= '7'){
 1405                      if(!(state & a_SKIPMASK) && (ui8_t)c2 > 0x1F){
 1406                         rv |= n_SHEXP_STATE_ERR_NUMBER;
 1407                         --il, ++ib;
 1408                         if(flags & n_SHEXP_PARSE_LOG)
 1409                            n_err(_("\\0 argument exceeds a byte: %.*s: %.*s\n"),
 1410                               (int)input->l, input->s,
 1411                               (int)PTR2SIZE(ib - ib_save), ib_save);
 1412                         /* Write unchanged */
 1413 jerr_ib_save:
 1414                         rv |= n_SHEXP_STATE_OUTPUT;
 1415                         if(!(flags & n_SHEXP_PARSE_DRYRUN))
 1416                            store = n_string_push_buf(store, ib_save,
 1417                                  PTR2SIZE(ib - ib_save));
 1418                         continue;
 1419                      }
 1420                      c2 = (c2 << 3) | (c -= '0');
 1421                      --il, ++ib;
 1422                   }
 1423                   if(state & a_SKIPMASK)
 1424                      continue;
 1425                   if((c = c2) == '\0'){
 1426                      state |= a_SKIPQ;
 1427                      continue;
 1428                   }
 1429                   break;
 1430 
 1431                /* ISO 10646 / Unicode sequence, 8 or 4 hexadecimal bytes */
 1432                case 'U':
 1433                   i = 8;
 1434                   if(0){
 1435                   /* FALLTHRU */
 1436                case 'u':
 1437                      i = 4;
 1438                   }
 1439                   if(il == 0)
 1440                      goto j_dollar_ungetc;
 1441                   if(0){
 1442                      /* FALLTHRU */
 1443 
 1444                /* Hexadecimal sequence, 1 or 2 hexadecimal bytes */
 1445                case 'X':
 1446                case 'x':
 1447                      if(il == 0)
 1448                         goto j_dollar_ungetc;
 1449                      i = 2;
 1450                   }
 1451                   /* C99 */{
 1452                      static ui8_t const hexatoi[] = { /* XXX uses ASCII */
 1453                         0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
 1454                      };
 1455                      size_t no, j;
 1456 
 1457                      i = n_MIN(il, i);
 1458                      for(no = j = 0; i-- > 0; --il, ++ib, ++j){
 1459                         c = *ib;
 1460                         if(hexchar(c)){
 1461                            no <<= 4;
 1462                            no += hexatoi[(ui8_t)((c) - ((c) <= '9' ? 48
 1463                                  : ((c) <= 'F' ? 55 : 87)))];
 1464                         }else if(j == 0){
 1465                            if(state & a_SKIPMASK)
 1466                               break;
 1467                            c2 = (c2 == 'U' || c2 == 'u') ? 'u' : 'x';
 1468                            if(flags & n_SHEXP_PARSE_LOG)
 1469                               n_err(_("Invalid \\%c notation: %.*s: %.*s\n"),
 1470                                  c2, (int)input->l, input->s,
 1471                                  (int)PTR2SIZE(ib - ib_save), ib_save);
 1472                            rv |= n_SHEXP_STATE_ERR_NUMBER;
 1473                            goto jerr_ib_save;
 1474                         }else
 1475                            break;
 1476                      }
 1477 
 1478                      /* Unicode massage */
 1479                      if((c2 != 'U' && c2 != 'u') || n_uasciichar(no)){
 1480                         if((c = (char)no) == '\0')
 1481                            state |= a_SKIPQ;
 1482                      }else if(no == 0)
 1483                         state |= a_SKIPQ;
 1484                      else if(!(state & a_SKIPMASK)){
 1485                         if(!(flags & n_SHEXP_PARSE_DRYRUN))
 1486                            store = n_string_reserve(store, n_MAX(j, 4));
 1487 
 1488                         if(no > 0x10FFFF){ /* XXX magic; CText */
 1489                            if(flags & n_SHEXP_PARSE_LOG)
 1490                               n_err(_("\\U argument exceeds 0x10FFFF: %.*s: "
 1491                                     "%.*s\n"),
 1492                                  (int)input->l, input->s,
 1493                                  (int)PTR2SIZE(ib - ib_save), ib_save);
 1494                            rv |= n_SHEXP_STATE_ERR_NUMBER;
 1495                            /* But normalize the output anyway */
 1496                            goto Jerr_uni_norm;
 1497                         }
 1498 
 1499                         j = n_utf32_to_utf8(no, utf);
 1500 
 1501                         if(n_psonce & n_PSO_UNICODE){
 1502                            rv |= n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_UNICODE;
 1503                            if(!(flags & n_SHEXP_PARSE_DRYRUN))
 1504                               store = n_string_push_buf(store, utf, j);
 1505                            continue;
 1506                         }
 1507 #ifdef HAVE_ICONV
 1508                         else{
 1509                            char *icp;
 1510 
 1511                            icp = n_iconv_onetime_cp(n_ICONV_NONE,
 1512                                  NULL, NULL, utf);
 1513                            if(icp != NULL){
 1514                               rv |= n_SHEXP_STATE_OUTPUT;
 1515                               if(!(flags & n_SHEXP_PARSE_DRYRUN))
 1516                                  store = n_string_push_cp(store, icp);
 1517                               continue;
 1518                            }
 1519                         }
 1520 #endif
 1521                         if(!(flags & n_SHEXP_PARSE_DRYRUN)) Jerr_uni_norm:{
 1522                            char itoa[32];
 1523 
 1524                            rv |= n_SHEXP_STATE_OUTPUT |
 1525                                  n_SHEXP_STATE_ERR_UNICODE;
 1526                            i = snprintf(itoa, sizeof itoa, "\\%c%0*X",
 1527                                  (no > 0xFFFFu ? 'U' : 'u'),
 1528                                  (int)(no > 0xFFFFu ? 8 : 4), (ui32_t)no);
 1529                            store = n_string_push_buf(store, itoa, i);
 1530                         }
 1531                         continue;
 1532                      }
 1533                      if(state & a_SKIPMASK)
 1534                         continue;
 1535                   }
 1536                   break;
 1537 
 1538                /* Extension: \$ can be used to expand a variable.
 1539                 * B(ug|ad) effect: if conversion fails, not written "as-is" */
 1540                case '$':
 1541                   if(il == 0)
 1542                      goto j_dollar_ungetc;
 1543                   goto J_var_expand;
 1544 
 1545                default:
 1546 j_dollar_ungetc:
 1547                   /* Follow bash(1) behaviour, print sequence unchanged */
 1548                   ++il, --ib;
 1549                   break;
 1550                }
 1551             }
 1552          }else if(c == '$' && quotec == '"' && il > 0) J_var_expand:{
 1553             state &= ~a_VARSUBST_MASK;
 1554             if(*ib == '{')
 1555                state |= a_BRACE;
 1556 
 1557             /* Scan variable name */
 1558             if(!(state & a_BRACE) || il > 1){
 1559                char const *cp, *vp;
 1560 
 1561                ib_save = ib - 1;
 1562                if(state & a_BRACE)
 1563                   --il, ++ib;
 1564                vp = ib;
 1565                state &= ~a_EXPLODE;
 1566 
 1567                for(i = 0; il > 0; --il, ++ib, ++i){
 1568                   /* We have some special cases regarding special parameters,
 1569                    * so ensure these don't cause failure.  This code has
 1570                    * counterparts in code that manages internal variables! */
 1571                   c = *ib;
 1572                   if(!a_SHEXP_ISVARC(c)){
 1573                      if(i == 0){
 1574                         /* Simply skip over multiplexer */
 1575                         if(c == '^')
 1576                            continue;
 1577                         if(c == '*' || c == '@' || c == '#' || c == '?' ||
 1578                               c == '!'){
 1579                            if(c == '@'){
 1580                               if(quotec == '"')
 1581                                  state |= a_EXPLODE;
 1582                            }
 1583                            --il, ++ib;
 1584                            ++i;
 1585                         }
 1586                      }
 1587                      break;
 1588                   }else if(a_SHEXP_ISVARC_BAD1ST(c)){
 1589                      if(i == 0)
 1590                         state |= a_DIGIT1;
 1591                   }else
 1592                      state |= a_NONDIGIT;
 1593                }
 1594 
 1595                /* In skip mode, be easy and.. skip over */
 1596                if(state & a_SKIPMASK){
 1597                   if((state & a_BRACE) && il > 0 && *ib == '}')
 1598                      --il, ++ib;
 1599                   continue;
 1600                }
 1601 
 1602                /* Handle the scan error cases */
 1603                if((state & (a_DIGIT1 | a_NONDIGIT)) == (a_DIGIT1 | a_NONDIGIT)){
 1604                   if(state & a_BRACE){
 1605                      if(il > 0 && *ib == '}')
 1606                         --il, ++ib;
 1607                      else
 1608                         rv |= n_SHEXP_STATE_ERR_GROUPOPEN;
 1609                   }
 1610                   if(flags & n_SHEXP_PARSE_LOG)
 1611                      n_err(_("Invalid identifier for ${}: %.*s: %.*s\n"),
 1612                         (int)input->l, input->s,
 1613                         (int)PTR2SIZE(ib - ib_save), ib_save);
 1614                   rv |= n_SHEXP_STATE_ERR_IDENTIFIER;
 1615                   goto jerr_ib_save;
 1616                }else if(i == 0){
 1617                   if(state & a_BRACE){
 1618                      if(il == 0 || *ib != '}'){
 1619                         if(flags & n_SHEXP_PARSE_LOG)
 1620                            n_err(_("No closing brace for ${}: %.*s: %.*s\n"),
 1621                               (int)input->l, input->s,
 1622                               (int)PTR2SIZE(ib - ib_save), ib_save);
 1623                         rv |= n_SHEXP_STATE_ERR_GROUPOPEN;
 1624                         goto jerr_ib_save;
 1625                      }
 1626                      --il, ++ib;
 1627 
 1628                      if(i == 0){
 1629                         if(flags & n_SHEXP_PARSE_LOG)
 1630                            n_err(_("Bad substitution for ${}: %.*s: %.*s\n"),
 1631                               (int)input->l, input->s,
 1632                               (int)PTR2SIZE(ib - ib_save), ib_save);
 1633                         rv |= n_SHEXP_STATE_ERR_BADSUB;
 1634                         goto jerr_ib_save;
 1635                      }
 1636                   }
 1637                   /* Simply write dollar as-is? */
 1638                   c = '$';
 1639                }else{
 1640                   if(state & a_BRACE){
 1641                      if(il == 0 || *ib != '}'){
 1642                         if(flags & n_SHEXP_PARSE_LOG)
 1643                            n_err(_("No closing brace for ${}: %.*s: %.*s\n"),
 1644                               (int)input->l, input->s,
 1645                               (int)PTR2SIZE(ib - ib_save), ib_save);
 1646                         rv |= n_SHEXP_STATE_ERR_GROUPOPEN;
 1647                         goto jerr_ib_save;
 1648                      }
 1649                      --il, ++ib;
 1650 
 1651                      if(i == 0){
 1652                         if(flags & n_SHEXP_PARSE_LOG)
 1653                            n_err(_("Bad substitution for ${}: %.*s: %.*s\n"),
 1654                               (int)input->l, input->s,
 1655                               (int)PTR2SIZE(ib - ib_save), ib_save);
 1656                         rv |= n_SHEXP_STATE_ERR_BADSUB;
 1657                         goto jerr_ib_save;
 1658                      }
 1659                   }
 1660 
 1661                   if(flags & n_SHEXP_PARSE_DRYRUN)
 1662                      continue;
 1663 
 1664                   /* We may shall explode "${@}" to a series of successive,
 1665                    * properly quoted tokens (instead).  The first exploded
 1666                    * cookie will join with the current token */
 1667                   if(n_UNLIKELY(state & a_EXPLODE) &&
 1668                         !(flags & n_SHEXP_PARSE_DRYRUN) && cookie != NULL){
 1669                      if(n_var_vexplode(cookie))
 1670                         state |= a_COOKIE;
 1671                      /* On the other hand, if $@ expands to nothing and is the
 1672                       * sole content of this quote then act like the shell does
 1673                       * and throw away the entire atxplode construct */
 1674                      else if(!(rv & n_SHEXP_STATE_OUTPUT) &&
 1675                            il == 1 && *ib == '"' &&
 1676                            ib_save == &input->s[1] && ib_save[-1] == '"')
 1677                         ++ib, --il;
 1678                      else
 1679                         continue;
 1680                      input->s = n_UNCONST(ib);
 1681                      input->l = il;
 1682                      goto jrestart_empty;
 1683                   }
 1684 
 1685                   /* Check getenv(3) shall no internal variable exist!
 1686                    * XXX We have some common idioms, avoid memory for them
 1687                    * XXX Even better would be var_vlook_buf()! */
 1688                   if(i == 1){
 1689                      switch(*vp){
 1690                      case '?': vp = n_qm; break;
 1691                      case '!': vp = n_em; break;
 1692                      case '*': vp = n_star; break;
 1693                      case '@': vp = n_at; break;
 1694                      case '#': vp = n_ns; break;
 1695                      default: goto j_var_look_buf;
 1696                      }
 1697                   }else
 1698 j_var_look_buf:
 1699                      vp = savestrbuf(vp, i);
 1700 
 1701                   if((cp = n_var_vlook(vp, TRU1)) != NULL){
 1702                      rv |= n_SHEXP_STATE_OUTPUT;
 1703                      store = n_string_push_cp(store, cp);
 1704                      for(; (c = *cp) != '\0'; ++cp)
 1705                         if(cntrlchar(c)){
 1706                            rv |= n_SHEXP_STATE_CONTROL;
 1707                            break;
 1708                         }
 1709                   }
 1710                   continue;
 1711                }
 1712             }
 1713          }else if(c == '`' && quotec == '"' && il > 0){ /* TODO shell command */
 1714             continue;
 1715          }
 1716       }
 1717 
 1718       if(!(state & a_SKIPMASK)){
 1719          rv |= n_SHEXP_STATE_OUTPUT;
 1720          if(cntrlchar(c))
 1721             rv |= n_SHEXP_STATE_CONTROL;
 1722          if(!(flags & n_SHEXP_PARSE_DRYRUN))
 1723             store = n_string_push_c(store, c);
 1724       }
 1725    }
 1726 
 1727    if(quotec != '\0' && !(flags & n_SHEXP_PARSE_QUOTE_AUTO_CLOSE)){
 1728       if(flags & n_SHEXP_PARSE_LOG)
 1729          n_err(_("No closing quote: %.*s\n"), (int)input->l, input->s);
 1730       rv |= n_SHEXP_STATE_ERR_QUOTEOPEN;
 1731    }
 1732 
 1733 jleave:
 1734    assert(!(state & a_COOKIE));
 1735    if((flags & n_SHEXP_PARSE_DRYRUN) && store != NULL){
 1736       store = n_string_push_buf(store, input->s, PTR2SIZE(ib - input->s));
 1737       rv |= n_SHEXP_STATE_OUTPUT;
 1738    }
 1739 
 1740    if(state & a_CONSUME){
 1741       input->s = n_UNCONST(&ib[il]);
 1742       input->l = 0;
 1743    }else{
 1744       if(flags & n_SHEXP_PARSE_TRIM_SPACE){
 1745          for(; il > 0; ++ib, --il){
 1746             if(!blankspacechar(*ib))
 1747                break;
 1748             rv |= n_SHEXP_STATE_WS_TRAIL;
 1749          }
 1750       }
 1751 
 1752       if(flags & n_SHEXP_PARSE_TRIM_IFSSPACE){
 1753          for(; il > 0; ++ib, --il){
 1754             if(strchr(ifs_ws, *ib) == NULL)
 1755                break;
 1756             rv |= n_SHEXP_STATE_WS_TRAIL;
 1757          }
 1758       }
 1759 
 1760       input->l = il;
 1761       input->s = n_UNCONST(ib);
 1762    }
 1763 
 1764    if(!(rv & n_SHEXP_STATE_STOP)){
 1765       if(!(rv & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_META_MASK)) &&
 1766             (flags & n_SHEXP_PARSE_IGNORE_EMPTY) && il > 0)
 1767          goto jrestart_empty;
 1768       if(/*!(rv & n_SHEXP_STATE_OUTPUT) &&*/ il == 0)
 1769          rv |= n_SHEXP_STATE_STOP;
 1770    }
 1771 
 1772    if((state & a_SKIPT) && !(rv & n_SHEXP_STATE_STOP) &&
 1773          (flags & n_SHEXP_PARSE_META_MASK))
 1774       goto jrestart;
 1775 jleave_quick:
 1776    assert((rv & n_SHEXP_STATE_OUTPUT) || !(rv & n_SHEXP_STATE_UNICODE));
 1777    assert((rv & n_SHEXP_STATE_OUTPUT) || !(rv & n_SHEXP_STATE_CONTROL));
 1778    NYD2_LEAVE;
 1779    return rv;
 1780 }
 1781 
 1782 FL char *
 1783 n_shexp_parse_token_cp(enum n_shexp_parse_flags flags, char const **cp){
 1784    struct str input;
 1785    struct n_string sou, *soup;
 1786    char *rv;
 1787    enum n_shexp_state shs;
 1788    NYD2_ENTER;
 1789 
 1790    assert(cp != NULL);
 1791 
 1792    input.s = n_UNCONST(*cp);
 1793    input.l = UIZ_MAX;
 1794    soup = n_string_creat_auto(&sou);
 1795 
 1796    shs = n_shexp_parse_token(flags, soup, &input, NULL);
 1797    if(shs & n_SHEXP_STATE_ERR_MASK){
 1798       soup = n_string_assign_cp(soup, *cp);
 1799       *cp = NULL;
 1800    }else
 1801       *cp = input.s;
 1802 
 1803    rv = n_string_cp(soup);
 1804    /*n_string_gut(n_string_drop_ownership(soup));*/
 1805    NYD2_LEAVE;
 1806    return rv;
 1807 }
 1808 
 1809 FL struct n_string *
 1810 n_shexp_quote(struct n_string *store, struct str const *input, bool_t rndtrip){
 1811    struct a_shexp_quote_lvl sql;
 1812    struct a_shexp_quote_ctx sqc;
 1813    NYD2_ENTER;
 1814 
 1815    assert(store != NULL);
 1816    assert(input != NULL);
 1817    assert(input->l == 0 || input->s != NULL);
 1818 
 1819    memset(&sqc, 0, sizeof sqc);
 1820    sqc.sqc_store = store;
 1821    sqc.sqc_input.s = input->s;
 1822    if((sqc.sqc_input.l = input->l) == UIZ_MAX)
 1823       sqc.sqc_input.l = strlen(input->s);
 1824    sqc.sqc_flags = rndtrip ? a_SHEXP_QUOTE_ROUNDTRIP : a_SHEXP_QUOTE_NONE;
 1825 
 1826    if(sqc.sqc_input.l == 0)
 1827       store = n_string_push_buf(store, "''", sizeof("''") -1);
 1828    else{
 1829       memset(&sql, 0, sizeof sql);
 1830       sql.sql_dat = sqc.sqc_input;
 1831       sql.sql_flags = sqc.sqc_flags;
 1832       a_shexp__quote(&sqc, &sql);
 1833    }
 1834    NYD2_LEAVE;
 1835    return store;
 1836 }
 1837 
 1838 FL char *
 1839 n_shexp_quote_cp(char const *cp, bool_t rndtrip){
 1840    struct n_string store;
 1841    struct str input;
 1842    char *rv;
 1843    NYD2_ENTER;
 1844 
 1845    assert(cp != NULL);
 1846 
 1847    input.s = n_UNCONST(cp);
 1848    input.l = UIZ_MAX;
 1849    rv = n_string_cp(n_shexp_quote(n_string_creat_auto(&store), &input,
 1850          rndtrip));
 1851    n_string_gut(n_string_drop_ownership(&store));
 1852    NYD2_LEAVE;
 1853    return rv;
 1854 }
 1855 
 1856 FL bool_t
 1857 n_shexp_is_valid_varname(char const *name){
 1858    char lc, c;
 1859    bool_t rv;
 1860    NYD2_ENTER;
 1861 
 1862    rv = FAL0;
 1863 
 1864    for(lc = '\0'; (c = *name++) != '\0'; lc = c)
 1865       if(!a_SHEXP_ISVARC(c))
 1866          goto jleave;
 1867       else if(lc == '\0' && a_SHEXP_ISVARC_BAD1ST(c))
 1868          goto jleave;
 1869    if(a_SHEXP_ISVARC_BADNST(lc))
 1870       goto jleave;
 1871 
 1872    rv = TRU1;
 1873 jleave:
 1874    NYD2_LEAVE;
 1875    return rv;
 1876 }
 1877 
 1878 FL int
 1879 c_shcodec(void *vp){
 1880    struct str in;
 1881    struct n_string sou_b, *soup;
 1882    si32_t nerrn;
 1883    size_t alen;
 1884    bool_t norndtrip;
 1885    char const **argv, *varname, *act, *cp;
 1886 
 1887    soup = n_string_creat_auto(&sou_b);
 1888    argv = vp;
 1889    varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
 1890 
 1891    act = *argv;
 1892    for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
 1893       ;
 1894    if((norndtrip = (*act == '+')))
 1895       ++act;
 1896    if(act == cp)
 1897       goto jesynopsis;
 1898    alen = PTR2SIZE(cp - act);
 1899    if(*cp != '\0')
 1900       ++cp;
 1901 
 1902    in.l = strlen(in.s = n_UNCONST(cp));
 1903    nerrn = n_ERR_NONE;
 1904 
 1905    if(is_ascncaseprefix(act, "encode", alen))
 1906       soup = n_shexp_quote(soup, &in, !norndtrip);
 1907    else if(!norndtrip && is_ascncaseprefix(act, "decode", alen)){
 1908       for(;;){
 1909          enum n_shexp_state shs;
 1910 
 1911          shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG |
 1912                n_SHEXP_PARSE_IGNORE_EMPTY), soup, &in, NULL);
 1913          if(shs & n_SHEXP_STATE_ERR_MASK){
 1914             soup = n_string_assign_cp(soup, cp);
 1915             nerrn = n_ERR_CANCELED;
 1916             vp = NULL;
 1917             break;
 1918          }
 1919          if(shs & n_SHEXP_STATE_STOP)
 1920             break;
 1921       }
 1922    }else
 1923       goto jesynopsis;
 1924 
 1925    if(varname != NULL){
 1926       cp = n_string_cp(soup);
 1927       if(!n_var_vset(varname, (uintptr_t)cp)){
 1928          nerrn = n_ERR_NOTSUP;
 1929          vp = NULL;
 1930       }
 1931    }else{
 1932       struct str out;
 1933 
 1934       in.s = n_string_cp(soup);
 1935       in.l = soup->s_len;
 1936       makeprint(&in, &out);
 1937       if(fprintf(n_stdout, "%s\n", out.s) < 0){
 1938          nerrn = n_err_no;
 1939          vp = NULL;
 1940       }
 1941       free(out.s);
 1942    }
 1943 
 1944 jleave:
 1945    n_pstate_err_no = nerrn;
 1946    NYD_LEAVE;
 1947    return (vp != NULL ? 0 : 1);
 1948 jesynopsis:
 1949    n_err(_("Synopsis: shcodec: <[+]e[ncode]|d[ecode]> <rest-of-line>\n"));
 1950    nerrn = n_ERR_INVAL;
 1951    vp = NULL;
 1952    goto jleave;
 1953 }
 1954 
 1955 /* s-it-mode */