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 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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
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
39 #ifndef HAVE_AMALGAMATION
53 * Environment variable names used by the utilities in the Shell and
54 * Utilities volume of POSIX.1-2008 consist solely of uppercase
55 * letters, digits, and the <underscore> ('_') from the characters
56 * defined in Portable Character Set and do not begin with a digit.
57 * Other characters may be permitted by an implementation;
58 * applications shall tolerate the presence of such names.
59 * We do support the hyphen "-" because it is common for mailx. */
60 #define a_SHEXP_ISVARC(C) (alnumchar(C) || (C) == '_' || (C) == '-')
62 enum a_shexp_quote_flags
{
64 a_SHEXP_QUOTE_ROUNDTRIP
= 1u<<0, /* Result won't be consumed immediately */
66 a_SHEXP_QUOTE_T_REVSOL
= 1u<<8, /* Type: by reverse solidus */
67 a_SHEXP_QUOTE_T_SINGLE
= 1u<<9, /* Type: single-quotes */
68 a_SHEXP_QUOTE_T_DOUBLE
= 1u<<10, /* Type: double-quotes */
69 a_SHEXP_QUOTE_T_DOLLAR
= 1u<<11, /* Type: dollar-single-quotes */
70 a_SHEXP_QUOTE_T_MASK
= a_SHEXP_QUOTE_T_REVSOL
| a_SHEXP_QUOTE_T_SINGLE
|
71 a_SHEXP_QUOTE_T_DOUBLE
| a_SHEXP_QUOTE_T_DOLLAR
,
73 a_SHEXP_QUOTE__FREESHIFT
= 16u
76 struct a_shexp_var_stack
{
77 struct a_shexp_var_stack
*svs_next
; /* Outer stack frame */
78 char const *svs_value
; /* Remaining value to expand */
79 size_t svs_len
; /* gth of .svs_dat this level */
80 char const *svs_dat
; /* Result data of this level */
81 bool_t svs_bsesc
; /* Shall backslash escaping be performed */
86 struct a_shexp_glob_ctx
{
87 char const *sgc_patdat
; /* Remaining pattern (at and below level) */
89 struct n_string
*sgc_outer
; /* Resolved path up to this level */
95 struct a_shexp_quote_ctx
{
96 struct n_string
*sqc_store
; /* Result storage */
97 struct str sqc_input
; /* Input data, topmost level */
99 ui32_t sqc_cnt_single
;
100 ui32_t sqc_cnt_double
;
101 ui32_t sqc_cnt_dollar
;
102 enum a_shexp_quote_flags sqc_flags
;
106 struct a_shexp_quote_lvl
{
107 struct a_shexp_quote_lvl
*sql_link
; /* Outer level */
108 struct str sql_dat
; /* This level (has to) handle(d) */
109 enum a_shexp_quote_flags sql_flags
;
113 /* Locate the user's mailbox file (where new, unread mail is queued) */
114 static char * _findmail(char const *user
, bool_t force
);
116 /* Expand ^~/? and ^~USER/? constructs.
117 * Returns the completely resolved (maybe empty or identical to input)
118 * salloc()ed string */
119 static char *a_shexp_tilde(char const *s
);
121 /* (Try to) Expand any shell variable in s.
122 * Returns the completely resolved (maybe empty) salloc()ed string.
124 static char *a_shexp_var(struct a_shexp_var_stack
*svsp
);
126 /* Perform fnmatch(3). May return NULL on error */
127 static char *a_shexp_globname(char const *name
, enum fexp_mode fexpm
);
129 static bool_t
a_shexp__glob(struct a_shexp_glob_ctx
*sgcp
,
130 struct n_strlist
**slpp
);
131 static int a_shexp__globsort(void const *cvpa
, void const *cvpb
);
134 /* Parse an input string and create a sh(1)ell-quoted result */
135 static void a_shexp__quote(struct a_shexp_quote_ctx
*sqcp
,
136 struct a_shexp_quote_lvl
*sqlp
);
139 _findmail(char const *user
, bool_t force
)
145 if (force
|| (cp
= ok_vlook(MAIL
)) == NULL
) {
146 size_t ul
= strlen(user
), i
= sizeof(VAL_MAIL
) -1 + 1 + ul
+1;
149 memcpy(rv
, VAL_MAIL
, i
= sizeof(VAL_MAIL
));
151 memcpy(&rv
[++i
], user
, ul
+1);
152 } else if ((rv
= fexpand(cp
, FEXP_NSHELL
)) == NULL
)
159 a_shexp_tilde(char const *s
){
166 if(*(rp
= &s
[1]) == '/' || *rp
== '\0'){
170 if((rp
= strchr(np
= rp
, '/')) != NULL
){
171 nl
= PTR2SIZE(rp
- np
);
172 np
= savestrbuf(np
, nl
);
177 if((pwp
= getpwnam(np
)) == NULL
){
185 rv
= salloc(nl
+ 1 + rl
+1);
188 memcpy(rv
+ nl
, rp
, rl
);
198 a_shexp_var(struct a_shexp_var_stack
*svsp
)
200 struct a_shexp_var_stack next
, *np
, *tmp
;
202 char lc
, c
, *cp
, *rv
;
206 if (*(vp
= svsp
->svs_value
) != '$') {
207 bool_t bsesc
= svsp
->svs_bsesc
;
208 union {bool_t hadbs
; char c
;} u
= {FAL0
};
211 for (lc
= '\0', i
= 0; ((c
= *vp
) != '\0'); ++i
, ++vp
) {
212 if (c
== '$' && lc
!= '\\')
216 lc
= (lc
== '\\') ? (u
.hadbs
= TRU1
, '\0') : c
;
221 svsp
->svs_dat
= cp
= savestrbuf(svsp
->svs_dat
, i
);
223 for (lc
= '\0', rv
= cp
; (u
.c
= *cp
++) != '\0';) {
224 if (u
.c
!= '\\' || lc
== '\\')
226 lc
= (lc
== '\\') ? '\0' : u
.c
;
230 svsp
->svs_len
= PTR2SIZE(rv
- svsp
->svs_dat
);
233 if ((lc
= (*++vp
== '{')))
237 for (i
= 0; (c
= *vp
) != '\0'; ++i
, ++vp
)
238 if (!a_SHEXP_ISVARC(c
))
243 n_err(_("Variable name misses closing }: %s\n"),
245 svsp
->svs_len
= strlen(svsp
->svs_value
);
246 svsp
->svs_dat
= svsp
->svs_value
;
253 /* Check getenv(3) shall no internal variable exist! */
254 if ((rv
= vok_vlook(cp
= savestrbuf(svsp
->svs_dat
, i
))) != NULL
||
255 (rv
= getenv(cp
)) != NULL
)
256 svsp
->svs_len
= strlen(svsp
->svs_dat
= rv
);
258 svsp
->svs_len
= 0, svsp
->svs_dat
= UNCONST("");
263 /* That level made the great and completed encoding. Build result */
265 for (i
= 0, np
= svsp
, svsp
= NULL
; np
!= NULL
;) {
273 cp
= rv
= salloc(i
+1);
274 while (svsp
!= NULL
) {
276 svsp
= svsp
->svs_next
;
277 memcpy(cp
, np
->svs_dat
, np
->svs_len
);
286 memset(&next
, 0, sizeof next
);
287 next
.svs_next
= svsp
;
289 next
.svs_bsesc
= svsp
->svs_bsesc
;
290 rv
= a_shexp_var(&next
);
295 a_shexp_globname(char const *name
, enum fexp_mode fexpm
){
297 struct a_shexp_glob_ctx sgc
;
298 struct n_string outer
;
299 struct n_strlist
*slp
;
303 memset(&sgc
, 0, sizeof sgc
);
304 sgc
.sgc_patlen
= strlen(name
);
305 sgc
.sgc_patdat
= savestrbuf(name
, sgc
.sgc_patlen
);
306 sgc
.sgc_outer
= n_string_reserve(n_string_creat(&outer
), sgc
.sgc_patlen
);
307 sgc
.sgc_flags
= ((fexpm
& FEXP_SILENT
) != 0);
309 if(a_shexp__glob(&sgc
, &slp
))
313 n_string_gut(&outer
);
319 cp
= UNCONST(N_("File pattern does not match"));
321 }else if(slp
->sl_next
== NULL
)
322 cp
= savestrbuf(slp
->sl_dat
, slp
->sl_len
);
323 else if(fexpm
& FEXP_MULTIOK
){
324 struct n_strlist
**sorta
, *xslp
;
328 for(xslp
= slp
; xslp
!= NULL
; xslp
= xslp
->sl_next
){
330 l
+= xslp
->sl_len
+ 1;
333 sorta
= smalloc(sizeof(*sorta
) * no
);
335 for(xslp
= slp
; xslp
!= NULL
; xslp
= xslp
->sl_next
)
337 qsort(sorta
, no
, sizeof *sorta
, &a_shexp__globsort
);
341 for(i
= 0; i
< no
; ++i
){
343 memcpy(&cp
[l
], xslp
->sl_dat
, xslp
->sl_len
);
350 pstate
|= PS_EXPAND_MULTIRESULT
;
352 cp
= UNCONST(N_("File pattern matches multiple results"));
358 struct n_strlist
*tmp
= slp
;
367 if(!(fexpm
& FEXP_SILENT
)){
368 name
= n_shexp_quote_cp(name
, FAL0
);
369 n_err("%s: %s\n", V_(cp
), name
);
374 #else /* HAVE_FNMATCH */
377 if(!(fexpm
& FEXP_SILENT
))
378 n_err(_("No filename pattern (fnmatch(3)) support compiled in\n"));
379 return savestr(name
);
385 a_shexp__glob(struct a_shexp_glob_ctx
*sgcp
, struct n_strlist
**slpp
){
386 enum{a_SILENT
= 1<<0, a_DEEP
=1<<1, a_SALLOC
=1<<2};
388 struct a_shexp_glob_ctx nsgc
;
392 char const *ccp
, *myp
;
395 /* We need some special treatment for the outermost level */
396 if(!(sgcp
->sgc_flags
& a_DEEP
)){
397 if(sgcp
->sgc_patlen
> 0 && sgcp
->sgc_patdat
[0] == '/'){
398 myp
= n_string_cp(n_string_push_c(sgcp
->sgc_outer
, '/'));
404 myp
= n_string_cp(sgcp
->sgc_outer
);
405 old_outerlen
= sgcp
->sgc_outer
->s_len
;
407 /* Separate current directory/pattern level from any possible remaining
408 * pattern in order to be able to use it for fnmatch(3) */
409 if((ccp
= memchr(sgcp
->sgc_patdat
, '/', sgcp
->sgc_patlen
)) == NULL
)
413 nsgc
.sgc_flags
|= a_DEEP
;
414 sgcp
->sgc_patlen
= PTR2SIZE((nsgc
.sgc_patdat
= &ccp
[1]) -
415 &sgcp
->sgc_patdat
[0]);
416 nsgc
.sgc_patlen
-= sgcp
->sgc_patlen
;
418 if(sgcp
->sgc_patlen
> 0){
419 assert(sgcp
->sgc_patdat
[sgcp
->sgc_patlen
-1] == '/');
420 ((char*)UNCONST(sgcp
->sgc_patdat
))[--sgcp
->sgc_patlen
] = '\0';
424 /* Our current directory level */
425 /* xxx Plenty of room for optimizations, like quickshot lstat(2) which may
426 * xxx be the (sole) result depending on pattern surroundings, etc. */
427 if((dp
= opendir(myp
)) == NULL
){
430 switch((err
= errno
)){
432 ccp
= N_("cannot access paths under non-directory");
435 ccp
= N_("path component of (sub)pattern non-existent");
438 ccp
= N_("file permission for file (sub)pattern denied");
441 ccp
= N_("cannot handle file (sub)pattern");
446 /* As necessary, quote bytes in the current pattern */
452 for(need
= FAL0
, i
= 0, myp
= sgcp
->sgc_patdat
; *myp
!= '\0'; ++myp
)
454 case '\'': case '"': case '\\': case '$':
466 for(i
= 0, myp
= sgcp
->sgc_patdat
; *myp
!= '\0'; ++myp
)
468 case '\'': case '"': case '\\': case '$':
479 myp
= sgcp
->sgc_patdat
;
482 while((dep
= readdir(dp
)) != NULL
){
483 switch(fnmatch(myp
, dep
->d_name
, FNM_PATHNAME
| FNM_PERIOD
)){
485 /* A match expresses the desire to recurse if there is more pattern */
486 if(nsgc
.sgc_patlen
> 0){
489 n_string_push_cp((sgcp
->sgc_outer
->s_len
> 1
490 ? n_string_push_c(sgcp
->sgc_outer
, '/') : sgcp
->sgc_outer
),
494 #ifdef HAVE_DIRENT_TYPE
495 if(dep
->d_type
== DT_DIR
)
497 else if(dep
->d_type
== DT_LNK
|| dep
->d_type
== DT_UNKNOWN
)
502 if(stat(n_string_cp(sgcp
->sgc_outer
), &sb
)){
503 ccp
= N_("I/O error when querying file status");
505 }else if(S_ISDIR(sb
.st_mode
))
509 /* TODO We recurse with current dir FD open, which could E[MN]FILE!
510 * TODO Instead save away a list of such n_string's for later */
511 if(isdir
&& !a_shexp__glob(&nsgc
, slpp
)){
516 n_string_trunc(sgcp
->sgc_outer
, old_outerlen
);
518 struct n_strlist
*slp
;
521 i
= strlen(dep
->d_name
);
522 j
= (old_outerlen
> 0) ? old_outerlen
+ 1 + i
: i
;
523 slp
= n_STRLIST_MALLOC(j
);
525 slpp
= &slp
->sl_next
;
527 if((j
= old_outerlen
) > 0){
528 memcpy(&slp
->sl_dat
[0], sgcp
->sgc_outer
->s_dat
, j
);
529 if(slp
->sl_dat
[j
-1] != '/')
530 slp
->sl_dat
[j
++] = '/';
532 memcpy(&slp
->sl_dat
[j
], dep
->d_name
, i
);
533 slp
->sl_dat
[j
+= i
] = '\0';
540 ccp
= N_("fnmatch(3) cannot handle file (sub)pattern");
550 return (ccp
== NULL
);
553 if(!(sgcp
->sgc_flags
& a_SILENT
)){
556 if(sgcp
->sgc_outer
->s_len
> 0){
557 s2
= n_shexp_quote_cp(n_string_cp(sgcp
->sgc_outer
), FAL0
);
562 n_err("%s: %s%s%s\n", V_(ccp
), s2
, s3
,
563 n_shexp_quote_cp(sgcp
->sgc_patdat
, FAL0
));
569 a_shexp__globsort(void const *cvpa
, void const *cvpb
){
571 struct n_strlist
const * const *slpa
, * const *slpb
;
576 rv
= asccasecmp((*slpa
)->sl_dat
, (*slpb
)->sl_dat
);
580 #endif /* HAVE_FNMATCH */
583 a_shexp__quote(struct a_shexp_quote_ctx
*sqcp
, struct a_shexp_quote_lvl
*sqlp
){
584 /* XXX Because of the problems caused by ISO C multibyte interface we cannot
585 * XXX use the recursive implementation because of stateful encodings.
586 * XXX I.e., if a quoted substring cannot be self-contained - the data after
587 * XXX the quote relies on "the former state", then this doesn't make sense.
588 * XXX Therefore this is not fully programmed out but instead only detects
589 * XXX the "most fancy" quoting necessary, and directly does that.
590 * XXX As a result of this, T_REVSOL and T_DOUBLE are not even considered.
591 * XXX Otherwise we rather have to convert to wide first and act on that,
592 * XXX e.g., call visual_info(n_VISUAL_INFO_WOUT_CREATE) on entire input */
593 #undef a_SHEXP_QUOTE_RECURSE /* XXX (Needs complete revisit, then) */
594 #ifdef a_SHEXP_QUOTE_RECURSE
595 # define jrecurse jrecurse
596 struct a_shexp_quote_lvl sql
;
598 # define jrecurse jstep
600 struct n_visual_info_ctx vic
;
601 union {struct a_shexp_quote_lvl
*head
; struct n_string
*store
;} u
;
607 ib
= sqlp
->sql_dat
.s
;
608 il
= sqlp
->sql_dat
.l
;
609 flags
= sqlp
->sql_flags
;
611 /* Iterate over the entire input, classify characters and type of quotes
612 * along the way. Whenever a quote change has to be applied, adjust flags
613 * for the new situation -, setup sql.* and recurse- */
619 if(flags
& a_SHEXP_QUOTE_T_DOLLAR
)
621 if(c
== '\t' && (flags
& (a_SHEXP_QUOTE_T_REVSOL
|
622 a_SHEXP_QUOTE_T_SINGLE
| a_SHEXP_QUOTE_T_DOUBLE
)))
624 #ifdef a_SHEXP_QUOTE_RECURSE
625 ++sqcp
->sqc_cnt_dollar
;
627 flags
= (flags
& ~a_SHEXP_QUOTE_T_MASK
) | a_SHEXP_QUOTE_T_DOLLAR
;
629 }else if(blankspacechar(c
) || c
== '"' || c
== '$'){
630 if(flags
& a_SHEXP_QUOTE_T_MASK
)
632 #ifdef a_SHEXP_QUOTE_RECURSE
633 ++sqcp
->sqc_cnt_single
;
635 flags
= (flags
& ~a_SHEXP_QUOTE_T_MASK
) | a_SHEXP_QUOTE_T_SINGLE
;
638 if(flags
& (a_SHEXP_QUOTE_T_MASK
& ~a_SHEXP_QUOTE_T_SINGLE
))
640 #ifdef a_SHEXP_QUOTE_RECURSE
641 ++sqcp
->sqc_cnt_dollar
;
643 flags
= (flags
& ~a_SHEXP_QUOTE_T_MASK
) | a_SHEXP_QUOTE_T_DOLLAR
;
646 if(flags
& a_SHEXP_QUOTE_T_MASK
)
648 #ifdef a_SHEXP_QUOTE_RECURSE
649 ++sqcp
->sqc_cnt_single
;
651 flags
= (flags
& ~a_SHEXP_QUOTE_T_MASK
) | a_SHEXP_QUOTE_T_SINGLE
;
653 }else if(!asciichar(c
)){
654 /* Need to keep together multibytes */
655 #ifdef a_SHEXP_QUOTE_RECURSE
656 memset(&vic
, 0, sizeof vic
);
660 n_VISUAL_INFO_ONE_CHAR
| n_VISUAL_INFO_SKIP_ERRORS
);
662 /* xxx check whether resulting \u would be ASCII */
663 if(!(flags
& a_SHEXP_QUOTE_ROUNDTRIP
) ||
664 (flags
& a_SHEXP_QUOTE_T_DOLLAR
)){
665 #ifdef a_SHEXP_QUOTE_RECURSE
673 #ifdef a_SHEXP_QUOTE_RECURSE
674 ++sqcp
->sqc_cnt_dollar
;
676 flags
= (flags
& ~a_SHEXP_QUOTE_T_MASK
) | a_SHEXP_QUOTE_T_DOLLAR
;
682 sqlp
->sql_flags
= flags
;
684 /* Level made the great and completed processing input. Reverse the list of
685 * levels, detect the "most fancy" quote type needed along this way */
686 /* XXX Due to restriction as above very crude */
687 for(flags
= 0, il
= 0, u
.head
= NULL
; sqlp
!= NULL
;){
688 struct a_shexp_quote_lvl
*tmp
;
690 tmp
= sqlp
->sql_link
;
691 sqlp
->sql_link
= u
.head
;
693 il
+= sqlp
->sql_dat
.l
;
694 if(sqlp
->sql_flags
& a_SHEXP_QUOTE_T_MASK
)
695 il
+= (sqlp
->sql_dat
.l
>> 1);
696 flags
|= sqlp
->sql_flags
;
701 /* Finally work the substrings in the correct order, adjusting quotes along
702 * the way as necessary. Start off with the "most fancy" quote, so that
703 * the user sees an overall boundary she can orientate herself on.
704 * We do it like that to be able to give the user some "encapsulation
705 * experience", to address what strikes me is a problem of sh(1)ell quoting:
706 * different to, e.g., perl(1), where you see at a glance where a string
707 * starts and ends, sh(1) quoting occurs at the "top level", disrupting the
708 * visual appearance of "a string" as such */
709 u
.store
= n_string_reserve(sqcp
->sqc_store
, il
);
711 if(flags
& a_SHEXP_QUOTE_T_DOLLAR
){
712 u
.store
= n_string_push_buf(u
.store
, "$'", sizeof("$'") -1);
713 flags
= (flags
& ~a_SHEXP_QUOTE_T_MASK
) | a_SHEXP_QUOTE_T_DOLLAR
;
714 }else if(flags
& a_SHEXP_QUOTE_T_DOUBLE
){
715 u
.store
= n_string_push_c(u
.store
, '"');
716 flags
= (flags
& ~a_SHEXP_QUOTE_T_MASK
) | a_SHEXP_QUOTE_T_DOUBLE
;
717 }else if(flags
& a_SHEXP_QUOTE_T_SINGLE
){
718 u
.store
= n_string_push_c(u
.store
, '\'');
719 flags
= (flags
& ~a_SHEXP_QUOTE_T_MASK
) | a_SHEXP_QUOTE_T_SINGLE
;
720 }else /*if(flags & a_SHEXP_QUOTE_T_REVSOL)*/
721 flags
&= ~a_SHEXP_QUOTE_T_MASK
;
723 /* Work all the levels */
724 for(; sqlp
!= NULL
; sqlp
= sqlp
->sql_link
){
725 /* As necessary update our mode of quoting */
726 #ifdef a_SHEXP_QUOTE_RECURSE
729 switch(sqlp
->sql_flags
& a_SHEXP_QUOTE_T_MASK
){
730 case a_SHEXP_QUOTE_T_DOLLAR
:
731 if(!(flags
& a_SHEXP_QUOTE_T_DOLLAR
))
732 il
= a_SHEXP_QUOTE_T_DOLLAR
;
734 case a_SHEXP_QUOTE_T_DOUBLE
:
735 if(!(flags
& (a_SHEXP_QUOTE_T_DOUBLE
| a_SHEXP_QUOTE_T_DOLLAR
)))
736 il
= a_SHEXP_QUOTE_T_DOLLAR
;
738 case a_SHEXP_QUOTE_T_SINGLE
:
739 if(!(flags
& (a_SHEXP_QUOTE_T_REVSOL
| a_SHEXP_QUOTE_T_SINGLE
|
740 a_SHEXP_QUOTE_T_DOUBLE
| a_SHEXP_QUOTE_T_DOLLAR
)))
741 il
= a_SHEXP_QUOTE_T_SINGLE
;
744 case a_SHEXP_QUOTE_T_REVSOL
:
745 if(!(flags
& (a_SHEXP_QUOTE_T_REVSOL
| a_SHEXP_QUOTE_T_SINGLE
|
746 a_SHEXP_QUOTE_T_DOUBLE
| a_SHEXP_QUOTE_T_DOLLAR
)))
747 il
= a_SHEXP_QUOTE_T_REVSOL
;
752 if(flags
& (a_SHEXP_QUOTE_T_SINGLE
| a_SHEXP_QUOTE_T_DOLLAR
))
753 u
.store
= n_string_push_c(u
.store
, '\'');
754 else if(flags
& a_SHEXP_QUOTE_T_DOUBLE
)
755 u
.store
= n_string_push_c(u
.store
, '"');
756 flags
&= ~a_SHEXP_QUOTE_T_MASK
;
759 if(flags
& a_SHEXP_QUOTE_T_DOLLAR
)
760 u
.store
= n_string_push_buf(u
.store
, "$'", sizeof("$'") -1);
761 else if(flags
& a_SHEXP_QUOTE_T_DOUBLE
)
762 u
.store
= n_string_push_c(u
.store
, '"');
763 else if(flags
& a_SHEXP_QUOTE_T_SINGLE
)
764 u
.store
= n_string_push_c(u
.store
, '\'');
766 #endif /* a_SHEXP_QUOTE_RECURSE */
768 /* Work the level's substring */
769 ib
= sqlp
->sql_dat
.s
;
770 il
= sqlp
->sql_dat
.l
;
778 assert(c
== '\t' || (flags
& a_SHEXP_QUOTE_T_DOLLAR
));
779 assert((flags
& (a_SHEXP_QUOTE_T_REVSOL
| a_SHEXP_QUOTE_T_SINGLE
|
780 a_SHEXP_QUOTE_T_DOUBLE
| a_SHEXP_QUOTE_T_DOLLAR
)));
782 case 0x07: c
= 'a'; break;
783 case 0x08: c
= 'b'; break;
784 case 0x0A: c
= 'n'; break;
785 case 0x0B: c
= 'v'; break;
786 case 0x0C: c
= 'f'; break;
787 case 0x0D: c
= 'r'; break;
788 case 0x1B: c
= 'E'; break;
791 if(flags
& a_SHEXP_QUOTE_T_DOLLAR
){
795 if(flags
& a_SHEXP_QUOTE_T_REVSOL
)
796 u
.store
= n_string_push_c(u
.store
, '\\');
799 u
.store
= n_string_push_c(u
.store
, '\\');
801 u
.store
= n_string_push_c(u
.store
, 'c');
805 }else if(blankspacechar(c
) || c
== '"' || c
== '$'){
806 if(flags
& (a_SHEXP_QUOTE_T_SINGLE
| a_SHEXP_QUOTE_T_DOLLAR
))
808 assert(flags
& (a_SHEXP_QUOTE_T_REVSOL
| a_SHEXP_QUOTE_T_DOUBLE
));
809 u
.store
= n_string_push_c(u
.store
, '\\');
812 if(flags
& a_SHEXP_QUOTE_T_DOUBLE
)
814 assert(!(flags
& a_SHEXP_QUOTE_T_SINGLE
));
815 u
.store
= n_string_push_c(u
.store
, '\\');
818 if(flags
& a_SHEXP_QUOTE_T_SINGLE
)
820 assert(flags
& (a_SHEXP_QUOTE_T_REVSOL
| a_SHEXP_QUOTE_T_DOUBLE
|
821 a_SHEXP_QUOTE_T_DOLLAR
));
822 u
.store
= n_string_push_c(u
.store
, '\\');
824 }else if(asciichar(c
)){
825 /* Shorthand: we can simply push that thing out */
827 u
.store
= n_string_push_c(u
.store
, c
);
830 /* Not an ASCII character, take care not to split up multibyte
832 #ifdef HAVE_NATCH_CHAR
833 if(options
& OPT_UNICODE
){
840 if((uc
= n_utf8_to_utf32(&ib2
, &il2
)) != UI32_MAX
){
844 il2
= PTR2SIZE(&ib2
[0] - &ib
[0]);
845 if((flags
& a_SHEXP_QUOTE_ROUNDTRIP
) || uc
== 0xFFFDu
){
846 /* Use padding to make ambiguities impossible */
847 il3
= snprintf(itoa
, sizeof itoa
, "\\%c%0*X",
848 (uc
> 0xFFFFu
? 'U' : 'u'),
849 (int)(uc
> 0xFFFFu
? 8 : 4), uc
);
855 u
.store
= n_string_push_buf(u
.store
, cp
, il3
);
856 ib
+= il2
, il
-= il2
;
860 #endif /* HAVE_NATCH_CHAR */
862 memset(&vic
, 0, sizeof vic
);
866 n_VISUAL_INFO_ONE_CHAR
| n_VISUAL_INFO_SKIP_ERRORS
);
868 /* Work this substring as sensitive as possible */
870 if(!(flags
& a_SHEXP_QUOTE_ROUNDTRIP
))
871 u
.store
= n_string_push_buf(u
.store
, ib
, il
);
873 else if((vic
.vic_indat
= n_iconv_onetime_cp(n_ICONV_NONE
,
874 "utf-8", charset_get_lc(), savestrbuf(ib
, il
))) != NULL
){
879 il3
= il2
= strlen(ib2
= vic
.vic_indat
);
880 if((uc
= n_utf8_to_utf32(&ib2
, &il2
)) != UI32_MAX
){
883 il2
= PTR2SIZE(&ib2
[0] - &vic
.vic_indat
[0]);
884 /* Use padding to make ambiguities impossible */
885 il3
= snprintf(itoa
, sizeof itoa
, "\\%c%0*X",
886 (uc
> 0xFFFFu
? 'U' : 'u'),
887 (int)(uc
> 0xFFFFu
? 8 : 4), uc
);
888 u
.store
= n_string_push_buf(u
.store
, itoa
, il3
);
898 u
.store
= n_string_push_buf(u
.store
, "\\xFF",
900 n_c_to_hex_base16(&u
.store
->s_dat
[u
.store
->s_len
- 2], *ib
++);
909 /* Close an open quote */
910 if(flags
& (a_SHEXP_QUOTE_T_SINGLE
| a_SHEXP_QUOTE_T_DOLLAR
))
911 u
.store
= n_string_push_c(u
.store
, '\'');
912 else if(flags
& a_SHEXP_QUOTE_T_DOUBLE
)
913 u
.store
= n_string_push_c(u
.store
, '"');
914 #ifdef a_SHEXP_QUOTE_RECURSE
920 #ifdef a_SHEXP_QUOTE_RECURSE
922 sqlp
->sql_dat
.l
-= il
;
925 sql
.sql_dat
.s
= UNCONST(ib
);
927 sql
.sql_flags
= flags
;
928 a_shexp__quote(sqcp
, &sql
);
933 #undef a_SHEXP_QUOTE_RECURSE
937 fexpand(char const *name
, enum fexp_mode fexpm
)
940 char const *cp
, *res
;
944 pstate
&= ~PS_EXPAND_MULTIRESULT
;
946 /* The order of evaluation is "%" and "#" expand into constants.
947 * "&" can expand into "+". "+" can expand into shell meta characters.
948 * Shell meta characters expand into constants.
949 * This way, we make no recursive expansion */
950 if ((fexpm
& FEXP_NSHORTCUT
) || (res
= shortcut_expand(name
)) == NULL
)
957 if (res
[1] == ':' && res
[2] != '\0') {
961 res
= _findmail((res
[1] != '\0' ? res
+ 1 : myname
), (res
[1] != '\0'));
966 if (prevfile
[0] == '\0') {
967 n_err(_("No previous file\n"));
975 res
= ok_vlook(MBOX
);
979 /* POSIX: if *folder* unset or null, "+" shall be retained */
980 if (*res
== '+' && *(cp
= folder_query()) != '\0') {
981 size_t i
= strlen(cp
);
983 res
= str_concat_csvl(&s
, cp
,
984 ((i
== 0 || cp
[i
-1] == '/') ? "" : "/"), res
+ 1, NULL
)->s
;
987 /* TODO *folder* can't start with %[:], can it!?! */
988 if (res
[0] == '%' && res
[1] == ':') {
994 /* Do some meta expansions */
995 if((fexpm
& (FEXP_NSHELL
| FEXP_NVAR
)) != FEXP_NVAR
&&
996 ((fexpm
& FEXP_NSHELL
) ? (strchr(res
, '$') != NULL
)
997 : anyof(res
, "{}[]*?$"))){
1000 if(fexpm
& FEXP_NOPROTO
)
1002 else switch(which_protocol(res
)){
1013 struct a_shexp_var_stack top
;
1015 memset(&top
, 0, sizeof top
);
1016 top
.svs_value
= res
;
1017 top
.svs_bsesc
= TRU1
;
1018 res
= a_shexp_var(&top
);
1021 res
= a_shexp_tilde(res
);
1023 if(!(fexpm
& FEXP_NSHELL
) &&
1024 (res
= a_shexp_globname(res
, fexpm
)) == NULL
)
1027 }/* else no tilde */
1028 }else if(res
[0] == '~'){
1029 res
= a_shexp_tilde(res
);
1034 if (fexpm
& FEXP_LOCAL
)
1035 switch (which_protocol(res
)) {
1040 n_err(_("Not a local file or directory: %s\n"),
1041 n_shexp_quote_cp(name
, FAL0
));
1047 if(res
!= NULL
&& !dyn
)
1050 return UNCONST(res
);
1054 n_shexp_expand_escape(char const **s
, bool_t use_nail_extensions
)/* TODO DROP!*/
1062 if ((c
= *xs
& 0xFF) == '\0')
1068 switch ((c
= *xs
& 0xFF)) {
1069 case 'a': c
= '\a'; break;
1070 case 'b': c
= '\b'; break;
1071 case 'c': c
= PROMPT_STOP
; break;
1072 case 'f': c
= '\f'; break;
1073 case 'n': c
= '\n'; break;
1074 case 'r': c
= '\r'; break;
1075 case 't': c
= '\t'; break;
1076 case 'v': c
= '\v'; break;
1084 /* Hexadecimal TODO uses ASCII */
1087 static ui8_t
const hexatoi
[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
1090 hexatoi[(ui8_t)((n) - ((n) <= '9' ? 48 : ((n) <= 'F' ? 55 : 87)))]
1098 if(options
& OPT_D_V
)
1099 n_err(_("Invalid \\xNUMBER notation in: %s\n"), xs
- 1);
1113 /* octal, with optional 0 prefix */
1123 for (c
= 0, n
= 3; n
-- > 0 && octalchar(*xs
); ++xs
) {
1129 /* S-nail extension for nice (get)prompt(()) support */
1134 if (use_nail_extensions
) {
1136 case '&': c
= ok_blook(bsdcompat
) ? '&' : '?'; break;
1137 case '?': c
= (pstate
& PS_EVAL_ERROR
) ? '1' : '0'; break;
1138 case '$': c
= PROMPT_DOLLAR
; break;
1139 case '@': c
= PROMPT_AT
; break;
1146 /* A sole <backslash> at EOS is treated as-is! */
1160 FL
enum n_shexp_state
1161 n_shexp_parse_token(struct n_string
*store
, struct str
*input
, /* TODO WCHAR */
1162 enum n_shexp_parse_flags flags
){
1163 #if defined HAVE_NATCH_CHAR || defined HAVE_ICONV
1169 a_SKIPQ
= 1<<0, /* Skip rest of this quote (\c0 ..) */
1170 a_SURPLUS
= 1<<1, /* Extended sequence interpretation */
1171 a_NTOKEN
= 1<<2 /* "New token": e.g., comments are possible */
1173 enum n_shexp_state rv
;
1175 char const *ib_save
, *ib
;
1179 assert((flags
& n_SHEXP_PARSE_DRYRUN
) || store
!= NULL
);
1180 assert(input
!= NULL
);
1181 assert(input
->l
== 0 || input
->s
!= NULL
);
1182 assert(!(flags
& n_SHEXP_PARSE_LOG
) || !(flags
& n_SHEXP_PARSE_LOG_D_V
));
1183 assert(!(flags
& n_SHEXP_PARSE_IFS_ADD_COMMA
) ||
1184 !(flags
& n_SHEXP_PARSE_IFS_IS_COMMA
));
1186 if((flags
& n_SHEXP_PARSE_LOG_D_V
) && (options
& OPT_D_V
))
1187 flags
|= n_SHEXP_PARSE_LOG
;
1189 if((flags
& n_SHEXP_PARSE_TRUNC
) && store
!= NULL
)
1190 store
= n_string_trunc(store
, 0);
1193 if((il
= input
->l
) == UIZ_MAX
)
1197 if(flags
& n_SHEXP_PARSE_TRIMSPACE
){
1198 for(; il
> 0; ++ib
, --il
)
1199 if(!blankspacechar(*ib
))
1202 input
->s
= UNCONST(ib
);
1206 rv
= n_SHEXP_STATE_STOP
;
1211 store
= n_string_reserve(store
, MIN(il
, 32)); /* XXX */
1213 for(rv
= n_SHEXP_STATE_NONE
, state
= a_NTOKEN
, quotec
= '\0'; il
> 0;){
1216 /* If no quote-mode active.. */
1218 if(c
== '"' || c
== '\''){
1223 state
&= ~a_SURPLUS
;
1237 }else if(c
== '\\'){
1238 /* Outside of quotes this just escapes any next character, but a sole
1239 * <backslash> at EOS is left unchanged */
1243 }else if(c
== '#' && (state
& a_NTOKEN
)){
1244 rv
|= n_SHEXP_STATE_STOP
;
1246 }else if(c
== ',' && (flags
&
1247 (n_SHEXP_PARSE_IFS_ADD_COMMA
| n_SHEXP_PARSE_IFS_IS_COMMA
)))
1249 else if(blankchar(c
)){
1250 if(!(flags
& n_SHEXP_PARSE_IFS_IS_COMMA
)){
1259 assert(!(state
& a_NTOKEN
));
1263 /* Users may need to recognize the presence of empty quotes */
1264 rv
|= n_SHEXP_STATE_OUTPUT
;
1266 }else if(c
== '\\' && (state
& a_SURPLUS
)){
1268 /* A sole <backslash> at EOS is treated as-is! This is ok since
1269 * the "closing quote" error will occur next, anyway */
1272 else if((c2
= *ib
) == quotec
){
1275 }else if(quotec
== '"'){
1277 * The <backslash> shall retain its special meaning as an
1278 * escape character (see Section 2.2.1) only when followed
1279 * by one of the following characters when considered
1280 * special: $ ` " \ <newline> */
1284 /* case '"': already handled via c2 == quotec */
1293 /* Dollar-single-quote */
1297 /* case '\'': already handled via c2 == quotec */
1302 case 'b': c
= '\b'; break;
1303 case 'f': c
= '\f'; break;
1304 case 'n': c
= '\n'; break;
1305 case 'r': c
= '\r'; break;
1306 case 't': c
= '\t'; break;
1307 case 'v': c
= '\v'; break;
1310 case 'e': c
= '\033'; break;
1312 /* Control character */
1315 goto j_dollar_ungetc
;
1319 c
= upperconv(c2
) ^ 0x40;
1320 if((ui8_t
)c
> 0x1F && c
!= 0x7F){ /* ASCII C0: 0..1F, 7F */
1321 if(flags
& n_SHEXP_PARSE_LOG
)
1322 n_err(_("Invalid \\c notation: %.*s\n"),
1323 (int)input
->l
, input
->s
);
1324 rv
|= n_SHEXP_STATE_ERR_CONTROL
;
1326 /* As an implementation-defined extension, support \c@
1327 * EQ printf(1) alike \c */
1329 rv
|= n_SHEXP_STATE_STOP
;
1334 /* Octal sequence: 1 to 3 octal bytes */
1336 /* As an extension (dependent on where you look, echo(1), or
1337 * awk(1)/tr(1) etc.), allow leading "0" octal indicator */
1338 if(il
> 0 && (c
= *ib
) >= '0' && c
<= '7'){
1343 case '1': case '2': case '3':
1344 case '4': case '5': case '6': case '7':
1346 if(il
> 0 && (c
= *ib
) >= '0' && c
<= '7'){
1347 c2
= (c2
<< 3) | (c
- '0');
1350 if(il
> 0 && (c
= *ib
) >= '0' && c
<= '7'){
1351 if((ui8_t
)c2
> 0x1F){
1352 if(flags
& n_SHEXP_PARSE_LOG
)
1353 n_err(_("\\0 argument exceeds a byte: %.*s\n"),
1354 (int)input
->l
, input
->s
);
1355 rv
|= n_SHEXP_STATE_ERR_NUMBER
;
1357 /* Write unchanged */
1359 rv
|= n_SHEXP_STATE_OUTPUT
;
1360 if(!(flags
& n_SHEXP_PARSE_DRYRUN
))
1361 store
= n_string_push_buf(store
, ib_save
,
1362 PTR2SIZE(ib
- ib_save
));
1365 c2
= (c2
<< 3) | (c
-= '0');
1368 if((c
= c2
) == '\0')
1374 /* ISO 10646 / Unicode sequence, 8 or 4 hexadecimal bytes */
1383 goto j_dollar_ungetc
;
1387 /* Hexadecimal sequence, 1 or 2 hexadecimal bytes */
1391 goto j_dollar_ungetc
;
1395 static ui8_t
const hexatoi
[] = { /* XXX uses ASCII */
1396 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
1401 for(no
= j
= 0; i
-- > 0; --il
, ++ib
, ++j
){
1405 no
+= hexatoi
[(ui8_t
)((c
) - ((c
) <= '9' ? 48
1406 : ((c
) <= 'F' ? 55 : 87)))];
1410 c2
= (c2
== 'U' || c2
== 'u') ? 'u' : 'x';
1411 if(flags
& n_SHEXP_PARSE_LOG
)
1412 n_err(_("Invalid \\%c notation: %.*s\n"),
1413 c2
, (int)input
->l
, input
->s
);
1414 rv
|= n_SHEXP_STATE_ERR_NUMBER
;
1420 /* Unicode massage */
1421 if((c2
!= 'U' && c2
!= 'u') || n_uasciichar(no
)){
1422 if((c
= (char)no
) == '\0')
1426 else if(!(state
& a_SKIPQ
)){
1427 if(!(flags
& n_SHEXP_PARSE_DRYRUN
))
1428 store
= n_string_reserve(store
, MAX(j
, 4));
1431 if(no
> 0x10FFFF){ /* XXX magic; CText */
1432 if(flags
& n_SHEXP_PARSE_LOG
)
1433 n_err(_("\\U argument exceeds 0x10FFFF: %.*s\n"),
1434 (int)input
->l
, input
->s
);
1435 rv
|= n_SHEXP_STATE_ERR_NUMBER
;
1436 /* But normalize the output anyway */
1440 #if defined HAVE_NATCH_CHAR || defined HAVE_ICONV
1441 j
= n_utf32_to_utf8(no
, utf
);
1443 #ifdef HAVE_NATCH_CHAR
1444 if(options
& OPT_UNICODE
){
1445 rv
|= n_SHEXP_STATE_OUTPUT
| n_SHEXP_STATE_UNICODE
;
1446 if(!(flags
& n_SHEXP_PARSE_DRYRUN
))
1447 store
= n_string_push_buf(store
, utf
, j
);
1455 icp
= n_iconv_onetime_cp(n_ICONV_NONE
,
1458 rv
|= n_SHEXP_STATE_OUTPUT
;
1459 if(!(flags
& n_SHEXP_PARSE_DRYRUN
))
1460 store
= n_string_push_cp(store
, icp
);
1465 if(!(flags
& n_SHEXP_PARSE_DRYRUN
)) Je_uni_norm
:{
1468 rv
|= n_SHEXP_STATE_OUTPUT
|
1469 n_SHEXP_STATE_ERR_UNICODE
;
1470 i
= snprintf(itoa
, sizeof itoa
, "\\%c%0*X",
1471 (no
> 0xFFFFu
? 'U' : 'u'),
1472 (int)(no
> 0xFFFFu
? 8 : 4), (ui32_t
)no
);
1473 store
= n_string_push_buf(store
, itoa
, i
);
1482 /* Extension: \$ can be used to expand a variable.
1483 * Bug|ad effect: if conversion fails, not written "as-is" */
1486 goto j_dollar_ungetc
;
1491 /* Follow bash behaviour, print sequence unchanged */
1496 }else if(c
== '$' && quotec
== '"' && il
> 0) J_var_expand
:{
1499 if(!(brace
= (*ib
== '{')) || il
> 1){
1500 char const *cp
, *vp
;
1506 for(i
= 0; il
> 0 && (c
= *ib
, a_SHEXP_ISVARC(c
)); ++i
)
1510 if(il
== 0 || *ib
!= '}'){
1511 if(state
& a_SKIPQ
){
1512 assert((state
& a_SURPLUS
) && quotec
== '\'');
1515 if(flags
& n_SHEXP_PARSE_LOG
)
1516 n_err(_("Closing brace missing for ${VAR}: %.*s\n"),
1517 (int)input
->l
, input
->s
);
1518 rv
|= n_SHEXP_STATE_ERR_QUOTEOPEN
|
1519 n_SHEXP_STATE_ERR_BRACE
;
1530 if(flags
& n_SHEXP_PARSE_LOG
)
1531 n_err(_("Bad substitution (${}): %.*s\n"),
1532 (int)input
->l
, input
->s
);
1533 rv
|= n_SHEXP_STATE_ERR_BADSUB
;
1537 }else if(flags
& n_SHEXP_PARSE_DRYRUN
)
1540 vp
= savestrbuf(vp
, i
);
1541 /* Check getenv(3) shall no internal variable exist! */
1542 if((cp
= vok_vlook(vp
)) != NULL
|| (cp
= getenv(vp
)) != NULL
){
1543 rv
|= n_SHEXP_STATE_OUTPUT
;
1544 store
= n_string_push_cp(store
, cp
);
1545 for(; (c
= *cp
) != '\0'; ++cp
)
1547 rv
|= n_SHEXP_STATE_CONTROL
;
1554 }else if(c
== '`' && quotec
== '"' && il
> 0){ /* TODO shell command */
1559 if(!(state
& a_SKIPQ
)){
1560 rv
|= n_SHEXP_STATE_OUTPUT
;
1562 rv
|= n_SHEXP_STATE_CONTROL
;
1563 if(!(flags
& n_SHEXP_PARSE_DRYRUN
))
1564 store
= n_string_push_c(store
, c
);
1569 if(flags
& n_SHEXP_PARSE_LOG
)
1570 n_err(_("no closing quote: %.*s\n"), (int)input
->l
, input
->s
);
1571 rv
|= n_SHEXP_STATE_ERR_QUOTEOPEN
;
1575 if((flags
& n_SHEXP_PARSE_DRYRUN
) && store
!= NULL
){
1576 store
= n_string_push_buf(store
, input
->s
, PTR2SIZE(ib
- input
->s
));
1577 rv
|= n_SHEXP_STATE_OUTPUT
;
1580 if(flags
& n_SHEXP_PARSE_TRIMSPACE
){
1581 for(; il
> 0; ++ib
, --il
)
1586 input
->s
= UNCONST(ib
);
1588 if(!(rv
& n_SHEXP_STATE_STOP
)){
1589 if(il
> 0 && !(rv
& n_SHEXP_STATE_OUTPUT
) &&
1590 (flags
& n_SHEXP_PARSE_IGNORE_EMPTY
))
1591 goto jrestart_empty
;
1592 if(!(rv
& n_SHEXP_STATE_OUTPUT
) && il
== 0)
1593 rv
|= n_SHEXP_STATE_STOP
;
1595 assert((rv
& n_SHEXP_STATE_OUTPUT
) || !(rv
& n_SHEXP_STATE_UNICODE
));
1596 assert((rv
& n_SHEXP_STATE_OUTPUT
) || !(rv
& n_SHEXP_STATE_CONTROL
));
1601 FL
enum n_shexp_state
1602 n_shexp_parse_token_buf(char **store
, char const *indat
, size_t inlen
,
1603 enum n_shexp_parse_flags flags
){
1606 enum n_shexp_state shs
;
1609 assert(store
!= NULL
);
1610 assert(inlen
== 0 || indat
!= NULL
);
1612 n_string_creat_auto(&ss
);
1613 is
.s
= UNCONST(indat
);
1616 shs
= n_shexp_parse_token(&ss
, &is
, flags
);
1618 shs
&= ~n_SHEXP_STATE_STOP
;
1620 shs
|= n_SHEXP_STATE_STOP
;
1621 *store
= n_string_cp(&ss
);
1622 n_string_drop_ownership(&ss
);
1629 FL
struct n_string
*
1630 n_shexp_quote(struct n_string
*store
, struct str
const *input
, bool_t rndtrip
){
1631 struct a_shexp_quote_lvl sql
;
1632 struct a_shexp_quote_ctx sqc
;
1635 assert(store
!= NULL
);
1636 assert(input
!= NULL
);
1637 assert(input
->l
== 0 || input
->s
!= NULL
);
1639 memset(&sqc
, 0, sizeof sqc
);
1640 sqc
.sqc_store
= store
;
1641 sqc
.sqc_input
.s
= input
->s
;
1642 if((sqc
.sqc_input
.l
= input
->l
) == UIZ_MAX
)
1643 sqc
.sqc_input
.l
= strlen(input
->s
);
1644 sqc
.sqc_flags
= rndtrip
? a_SHEXP_QUOTE_ROUNDTRIP
: a_SHEXP_QUOTE_NONE
;
1646 if(sqc
.sqc_input
.l
== 0)
1647 store
= n_string_push_buf(store
, "''", sizeof("''") -1);
1649 memset(&sql
, 0, sizeof sql
);
1650 sql
.sql_dat
= sqc
.sqc_input
;
1651 sql
.sql_flags
= sqc
.sqc_flags
;
1652 a_shexp__quote(&sqc
, &sql
);
1659 n_shexp_quote_cp(char const *cp
, bool_t rndtrip
){
1660 struct n_string store
;
1667 input
.s
= UNCONST(cp
);
1669 rv
= n_string_cp(n_shexp_quote(n_string_creat_auto(&store
), &input
,
1671 n_string_gut(n_string_drop_ownership(&store
));