n_shexp_parse_token(): (finally) get rid of CC warning
[s-mailx.git] / shexp.c
blob0b938dc2da7122283f70047befa3a879229e89ac
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.
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.
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.
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.
36 #undef n_FILE
37 #define n_FILE shexp
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 #include <pwd.h>
45 #ifdef HAVE_FNMATCH
46 # include <dirent.h>
47 # include <fnmatch.h>
48 #endif
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) == '-')
65 enum a_shexp_quote_flags{
66 a_SHEXP_QUOTE_NONE,
67 a_SHEXP_QUOTE_ROUNDTRIP = 1u<<0, /* Result won't be consumed immediately */
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,
76 a_SHEXP_QUOTE__FREESHIFT = 16u
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];
87 #endif
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];
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];
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);
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);
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
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);
127 static char *
128 a_shexp_findmail(char const *user, bool_t force){
129 char *rv;
130 char const *cp;
131 NYD2_ENTER;
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);
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;
152 #endif
154 if((cp = ok_vlook(MAIL)) != NULL){
155 rv = savestr(cp);
156 goto jleave;
160 /* C99 */{
161 size_t ul, i;
163 ul = strlen(user) +1;
164 i = sizeof(VAL_MAIL) -1 + 1 + ul;
166 rv = salloc(i);
167 memcpy(rv, VAL_MAIL, (i = sizeof(VAL_MAIL) -1));
168 rv[i] = '/';
169 memcpy(&rv[++i], user, ul);
171 jleave:
172 NYD2_LEAVE;
173 return rv;
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;
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;
195 if((pwp = getpwnam(np)) == NULL){
196 rv = savestr(s);
197 goto jleave;
199 np = pwp->pw_dir;
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;
209 rv[nl] = '\0';
210 jleave:
211 NYD2_LEAVE;
212 return rv;
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;
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);
236 if(cp == NULL)
237 goto jleave;
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;
248 no = l = 0;
249 for(xslp = slp; xslp != NULL; xslp = xslp->sl_next){
250 ++no;
251 l += xslp->sl_len + 1;
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);
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';
268 cp[l] = '\0';
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;
277 jleave:
278 while(slp != NULL){
279 struct n_strlist *tmp = slp;
281 slp = slp->sl_next;
282 free(tmp);
284 NYD_LEAVE;
285 return cp;
287 jerr:
288 if(!(fexpm & FEXP_SILENT)){
289 name = n_shexp_quote_cp(name, FAL0);
290 n_err("%s: %s\n", V_(cp), name);
292 cp = NULL;
293 goto jleave;
295 #else /* HAVE_FNMATCH */
296 n_UNUSED(fexpm);
298 if(!(fexpm & FEXP_SILENT))
299 n_err(_("No filename pattern (fnmatch(3)) support compiled in\n"));
300 return savestr(name);
301 #endif
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};
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;
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;
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';
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;
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;
371 /* As necessary, quote bytes in the current pattern */
372 /* C99 */{
373 char *ncp;
374 size_t i;
375 bool_t need;
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;
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;
401 ncp[i] = '\0';
402 myp = ncp;
403 }else
404 myp = sgcp->sgc_patdat;
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;
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);
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
425 struct stat sb;
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;
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;
441 n_string_trunc(sgcp->sgc_outer, old_outerlen);
442 }else{
443 struct n_strlist *slp;
444 size_t i, j;
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++] = '/';
457 memcpy(&slp->sl_dat[j], dep->d_name, i);
458 slp->sl_dat[j += i] = '\0';
459 slp->sl_len = j;
461 }break;
462 case FNM_NOMATCH:
463 break;
464 default:
465 ccp = N_("fnmatch(3) cannot handle file (sub)pattern");
466 goto jerr;
470 ccp = NULL;
471 jleave:
472 if(dp != NULL)
473 closedir(dp);
474 NYD2_LEAVE;
475 return (ccp == NULL);
477 jerr:
478 if(!(sgcp->sgc_flags & a_SILENT)){
479 char const *s2, *s3;
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;
487 n_err("%s: %s%s%s\n", V_(ccp), s2, s3,
488 n_shexp_quote_cp(sgcp->sgc_patdat, FAL0));
490 goto jleave;
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;
499 slpa = cvpa;
500 slpb = cvpb;
501 rv = asccasecmp((*slpa)->sl_dat, (*slpb)->sl_dat);
502 NYD2_LEAVE;
503 return rv;
505 #endif /* HAVE_FNMATCH */
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;
532 ib_base = ib = sqlp->sql_dat.s;
533 il = sqlp->sql_dat.l;
534 flags = sqlp->sql_flags;
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;
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
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;
610 sqlp->sql_flags = flags;
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;
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;
627 sqlp = u.head;
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);
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;
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;
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;
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;
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, '\'');
694 #endif /* a_SHEXP_QUOTE_RECURSE */
696 /* Work the level's substring */
697 ib = sqlp->sql_dat.s;
698 il = sqlp->sql_dat.l;
700 while(il > 0){
701 char c2, c;
703 c = *ib;
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;
723 if(flags & a_SHEXP_QUOTE_T_REVSOL)
724 u.store = n_string_push_c(u.store, '\\');
725 goto jpush;
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;
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;
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;
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];
786 u.store = n_string_push_buf(u.store, cp, il3);
787 ib += il2, il -= il2;
788 continue;
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);
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;
809 il2 = strlen(ib2 = vic.vic_indat);
810 if((uc = n_utf8_to_utf32(&ib2, &il2)) != UI32_MAX){
811 char itoa[32];
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;
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++);
833 ib = vic.vic_oudat;
834 il = vic.vic_oulen;
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;
850 #ifdef a_SHEXP_QUOTE_RECURSE
851 jrecurse:
852 sqlp->sql_dat.l -= il;
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
862 #undef jrecurse
863 #undef a_SHEXP_QUOTE_RECURSE
866 FL char *
867 fexpand(char const *name, enum fexp_mode fexpm) /* TODO in parts: -> URL::!! */
869 struct str proto, s;
870 char const *res, *cp;
871 bool_t dyn, haveproto;
872 NYD_ENTER;
874 n_pstate &= ~n_PS_EXPAND_MULTIRESULT;
875 dyn = FAL0;
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);
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;
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;
910 force = (res[1] != '\0');
911 res = a_shexp_findmail((force ? &res[1] : ok_vlook(LOGNAME)),
912 force);
913 if(force)
914 goto jislocal;
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;
925 res = prevfile;
926 goto jislocal;
927 case '&':
928 if (res[1] == '\0')
929 res = ok_vlook(MBOX);
930 break;
931 default:
932 break;
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;
942 #endif
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;
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;
957 if(fexpm & FEXP_NOPROTO)
958 doexp = TRU1;
959 else{
960 cp = haveproto ? savecat(savestrbuf(proto.s, proto.l), res) : res;
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;
973 if(doexp){
974 struct str shin;
975 struct n_string shou, *shoup;
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;
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;
990 res = n_string_cp(shoup);
991 /*shoup = n_string_drop_ownership(shoup);*/
992 dyn = TRU1;
994 if(res[0] == '~')
995 res = a_shexp_tilde(res);
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;
1007 jislocal:
1008 if(res != NULL && haveproto){
1009 res = savecat(savestrbuf(proto.s, proto.l), res);
1010 dyn = TRU1;
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;
1026 jleave:
1027 if(res != NULL && !dyn)
1028 res = savestr(res);
1029 NYD_LEAVE;
1030 return n_UNCONST(res);
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),
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;
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));
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;
1076 if((flags & n_SHEXP_PARSE_TRUNC) && store != NULL)
1077 store = n_string_trunc(store, 0);
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);
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');
1093 if(cookie != NULL && *cookie != NULL){
1094 assert(!(flags & n_SHEXP_PARSE_DRYRUN));
1095 state |= a_COOKIE;
1098 rv = n_SHEXP_STATE_NONE;
1099 jrestart_empty:
1100 rv &= n_SHEXP_STATE_WS_LEAD;
1101 state &= a_ROUND_MASK;
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;
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);
1119 for(cp = &n_string_cp(store)[i]; (c = *cp++) != '\0';)
1120 if(cntrlchar(c)){
1121 rv |= n_SHEXP_STATE_CONTROL;
1122 break;
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;
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;
1147 input->s = n_UNCONST(ib);
1148 input->l = il;
1151 if(il == 0){
1152 rv |= n_SHEXP_STATE_STOP;
1153 goto jleave;
1156 if(store != NULL)
1157 store = n_string_reserve(store, n_MIN(il, 32)); /* XXX */
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 = '\'';
1170 rv |= n_SHEXP_STATE_QUOTE;
1171 state |= a_SURPLUS;
1172 break;
1173 default:
1174 quotec = '\0';
1175 state |= a_NTOKEN;
1176 break;
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;
1188 while(il > 0){
1189 --il, c = *ib++;
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;
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;
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;
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;
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;
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);
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;
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;
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;
1291 state |= a_NTOKEN;
1292 }else
1293 state &= ~a_NTOKEN;
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;
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)
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;
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;
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;
1353 case 'E':
1354 case 'e': c = '\033'; break;
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;
1372 /* As an implementation-defined extension, support \c@
1373 * EQ printf(1) alike \c */
1374 if(c == '\0'){
1375 state |= a_SKIPT;
1376 continue;
1378 break;
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;
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;
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;
1412 c2 = (c2 << 3) | (c -= '0');
1413 --il, ++ib;
1415 if(state & a_SKIPMASK)
1416 continue;
1417 if((c = c2) == '\0'){
1418 state |= a_SKIPQ;
1419 continue;
1421 break;
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;
1431 if(il == 0)
1432 goto j_dollar_ungetc;
1433 if(0){
1434 /* FALLTHRU */
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;
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
1447 size_t no, j;
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;
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));
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;
1491 j = n_utf32_to_utf8(no, utf);
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;
1499 #ifdef HAVE_ICONV
1500 else{
1501 char *icp;
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;
1512 #endif
1513 if(!(flags & n_SHEXP_PARSE_DRYRUN)) Jerr_uni_norm:{
1514 char itoa[32];
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);
1523 continue;
1525 if(state & a_SKIPMASK)
1526 continue;
1528 break;
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;
1537 default:
1538 j_dollar_ungetc:
1539 /* Follow bash(1) behaviour, print sequence unchanged */
1540 ++il, --ib;
1541 break;
1544 }else if(c == '$' && quotec == '"' && il > 0) J_var_expand:{
1545 state &= ~a_VARSUBST_MASK;
1546 if(*ib == '{')
1547 state |= a_BRACE;
1549 /* Scan variable name */
1550 if(!(state & a_BRACE) || il > 1){
1551 char const *cp, *vp;
1553 ib_save = ib - 1;
1554 if(state & a_BRACE)
1555 --il, ++ib;
1556 vp = ib;
1557 state &= ~a_EXPLODE;
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;
1575 --il, ++ib;
1576 ++i;
1579 break;
1580 }else if(a_SHEXP_ISVARC_BAD1ST(c)){
1581 if(i == 0)
1582 state |= a_DIGIT1;
1583 }else
1584 state |= a_NONDIGIT;
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;
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;
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;
1618 --il, ++ib;
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;
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;
1641 --il, ++ib;
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;
1653 if(flags & n_SHEXP_PARSE_DRYRUN)
1654 continue;
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;
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;
1689 }else
1690 j_var_look_buf:
1691 vp = savestrbuf(vp, i);
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;
1702 continue;
1705 }else if(c == '`' && quotec == '"' && il > 0){ /* TODO shell command */
1706 continue;
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);
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;
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;
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;
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;
1752 input->l = il;
1753 input->s = n_UNCONST(ib);
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;
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;
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;
1782 assert(cp != NULL);
1784 input.s = n_UNCONST(*cp);
1785 input.l = UIZ_MAX;
1786 soup = n_string_creat_auto(&sou);
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;
1795 rv = n_string_cp(soup);
1796 /*n_string_gut(n_string_drop_ownership(soup));*/
1797 NYD2_LEAVE;
1798 return rv;
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;
1807 assert(store != NULL);
1808 assert(input != NULL);
1809 assert(input->l == 0 || input->s != NULL);
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;
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);
1826 NYD2_LEAVE;
1827 return store;
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;
1837 assert(cp != NULL);
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;
1848 FL bool_t
1849 n_shexp_is_valid_varname(char const *name){
1850 char lc, c;
1851 bool_t rv;
1852 NYD2_ENTER;
1854 rv = FAL0;
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;
1864 rv = TRU1;
1865 jleave:
1866 NYD2_LEAVE;
1867 return rv;
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;
1879 soup = n_string_creat_auto(&sou_b);
1880 argv = vp;
1881 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1883 act = *argv;
1884 for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
1886 if((norndtrip = (*act == '+')))
1887 ++act;
1888 if(act == cp)
1889 goto jesynopsis;
1890 alen = PTR2SIZE(cp - act);
1891 if(*cp != '\0')
1892 ++cp;
1894 in.l = strlen(in.s = n_UNCONST(cp));
1895 nerrn = n_ERR_NONE;
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;
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;
1911 if(shs & n_SHEXP_STATE_STOP)
1912 break;
1914 }else
1915 goto jesynopsis;
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;
1923 }else{
1924 struct str out;
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;
1933 free(out.s);
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;
1947 /* s-it-mode */