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