"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.7/shexp.c" (16 Feb 2018, 65487 Bytes) of package /linux/misc/s-nail-14.9.7.tar.xz:


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

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