1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Shell "word", file- and other name expansions.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 #ifndef HAVE_AMALGAMATION
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 "-" because it is common for mailx. */
58 #define a_SHEXP_ISVARC(C) (alnumchar(C) || (C) == '_' || (C) == '-')
61 struct shvar_stack
*shs_next
; /* Outer stack frame */
62 char const *shs_value
; /* Remaining value to expand */
63 size_t shs_len
; /* gth of .shs_dat this level */
64 char const *shs_dat
; /* Result data of this level */
65 bool_t
*shs_err
; /* Or NULL */
66 bool_t shs_bsesc
; /* Shall backslash escaping be performed */
69 /* Locate the user's mailbox file (where new, unread mail is queued) */
70 static char * _findmail(char const *user
, bool_t force
);
72 /* Perform shell meta character expansion TODO obsolete (INSECURE!) */
73 static char * _globname(char const *name
, enum fexp_mode fexpm
);
75 /* Perform shell variable expansion */
76 static char * _sh_exp_var(struct shvar_stack
*shsp
);
79 _findmail(char const *user
, bool_t force
)
85 if (force
|| (cp
= ok_vlook(MAIL
)) == NULL
) {
86 size_t ul
= strlen(user
), i
= sizeof(MAILSPOOL
) -1 + 1 + ul
+1;
89 memcpy(rv
, MAILSPOOL
, i
= sizeof(MAILSPOOL
));
91 memcpy(&rv
[++i
], user
, ul
+1);
92 } else if ((rv
= fexpand(cp
, FEXP_NSHELL
)) == NULL
)
99 _globname(char const *name
, enum fexp_mode fexpm
)
108 /* Mac OS X Snow Leopard and Linux don't init fields on error, causing
109 * SIGSEGV in wordfree(3); so let's just always zero it ourselfs */
110 memset(&we
, 0, sizeof we
);
112 /* Some systems (notably Open UNIX 8.0.0) fork a shell for wordexp()
113 * and wait, which will fail if our SIGCHLD handler is active */
115 sigaddset(&nset
, SIGCHLD
);
116 sigprocmask(SIG_BLOCK
, &nset
, NULL
);
118 # define WRDE_NOCMD 0
120 i
= wordexp(name
, &we
, WRDE_NOCMD
);
121 sigprocmask(SIG_UNBLOCK
, &nset
, NULL
);
128 if (!(fexpm
& FEXP_SILENT
))
129 n_err(_("\"%s\": Command substitution not allowed\n"), name
);
133 if (!(fexpm
& FEXP_SILENT
))
134 n_err(_("\"%s\": Expansion buffer overflow\n"), name
);
139 if (!(fexpm
& FEXP_SILENT
))
140 n_err(_("Syntax error in \"%s\"\n"), name
);
144 switch (we
.we_wordc
) {
146 cp
= savestr(we
.we_wordv
[0]);
149 if (!(fexpm
& FEXP_SILENT
))
150 n_err(_("\"%s\": No match\n"), name
);
153 if (fexpm
& FEXP_MULTIOK
) {
156 for (l
= 0, j
= 0; j
< we
.we_wordc
; ++j
)
157 l
+= strlen(we
.we_wordv
[j
]) + 1;
160 for (l
= 0, j
= 0; j
< we
.we_wordc
; ++j
) {
161 size_t x
= strlen(we
.we_wordv
[j
]);
162 memcpy(cp
+ l
, we
.we_wordv
[j
], x
);
167 } else if (!(fexpm
& FEXP_SILENT
))
168 n_err(_("\"%s\": Ambiguous\n"), name
);
176 #else /* HAVE_WORDEXP */
178 char xname
[PATH_MAX
+1], cmdbuf
[PATH_MAX
+1], /* also used for files */
180 int pivec
[2], pid
, l
, waits
;
183 if (pipe(pivec
) < 0) {
184 n_perr(_("pipe"), 0);
187 snprintf(cmdbuf
, sizeof cmdbuf
, "echo %s", name
);
188 pid
= start_command(ok_vlook(SHELL
), NULL
, COMMAND_FD_NULL
, pivec
[1],
189 "-c", cmdbuf
, NULL
, NULL
);
198 l
= read(pivec
[0], xname
, sizeof xname
);
202 n_perr(_("read"), 0);
207 if (!wait_child(pid
, &waits
) && WTERMSIG(waits
) != SIGPIPE
) {
208 if (!(fexpm
& FEXP_SILENT
))
209 n_err(_("\"%s\": Expansion failed\n"), name
);
213 if (!(fexpm
& FEXP_SILENT
))
214 n_err(_("\"%s\": No match\n"), name
);
217 if (l
== sizeof xname
) {
218 if (!(fexpm
& FEXP_SILENT
))
219 n_err(_("\"%s\": Expansion buffer overflow\n"), name
);
223 for (cp
= xname
+ l
- 1; *cp
== '\n' && cp
> xname
; --cp
)
226 if (!(fexpm
& FEXP_MULTIOK
) && strchr(xname
, ' ') != NULL
&&
227 stat(xname
, &sbuf
) < 0) {
228 if (!(fexpm
& FEXP_SILENT
))
229 n_err(_("\"%s\": Ambiguous\n"), name
);
237 #endif /* !HAVE_WORDEXP */
241 _sh_exp_var(struct shvar_stack
*shsp
)
243 struct shvar_stack next
, *np
, *tmp
;
245 char lc
, c
, *cp
, *rv
;
249 if (*(vp
= shsp
->shs_value
) != '$') {
250 bool_t bsesc
= shsp
->shs_bsesc
;
251 union {bool_t hadbs
; char c
;} u
= {FAL0
};
254 for (lc
= '\0', i
= 0; ((c
= *vp
) != '\0'); ++i
, ++vp
) {
255 if (c
== '$' && lc
!= '\\')
259 lc
= (lc
== '\\') ? (u
.hadbs
= TRU1
, '\0') : c
;
264 shsp
->shs_dat
= cp
= savestrbuf(shsp
->shs_dat
, i
);
266 for (lc
= '\0', rv
= cp
; (u
.c
= *cp
++) != '\0';) {
267 if (u
.c
!= '\\' || lc
== '\\')
269 lc
= (lc
== '\\') ? '\0' : u
.c
;
273 shsp
->shs_len
= PTR2SIZE(rv
- shsp
->shs_dat
);
276 if ((lc
= (*++vp
== '{')))
280 for (i
= 0; (c
= *vp
) != '\0'; ++i
, ++vp
)
281 if (!a_SHEXP_ISVARC(c
))
286 n_err(_("Variable name misses closing \"}\": \"%s\"\n"),
288 shsp
->shs_len
= strlen(shsp
->shs_value
);
289 shsp
->shs_dat
= shsp
->shs_value
;
290 if (shsp
->shs_err
!= NULL
)
291 *shsp
->shs_err
= TRU1
;
298 /* Check getenv(3) shall no internal variable exist! */
299 if ((rv
= vok_vlook(cp
= savestrbuf(shsp
->shs_dat
, i
))) != NULL
||
300 (rv
= getenv(cp
)) != NULL
)
301 shsp
->shs_len
= strlen(shsp
->shs_dat
= rv
);
303 shsp
->shs_len
= 0, shsp
->shs_dat
= UNCONST("");
308 /* That level made the great and completed encoding. Build result */
310 for (i
= 0, np
= shsp
, shsp
= NULL
; np
!= NULL
;) {
318 cp
= rv
= salloc(i
+1);
319 while (shsp
!= NULL
) {
321 shsp
= shsp
->shs_next
;
322 memcpy(cp
, np
->shs_dat
, np
->shs_len
);
331 memset(&next
, 0, sizeof next
);
332 next
.shs_next
= shsp
;
334 next
.shs_err
= shsp
->shs_err
;
335 next
.shs_bsesc
= shsp
->shs_bsesc
;
336 rv
= _sh_exp_var(&next
);
341 fexpand(char const *name
, enum fexp_mode fexpm
)
344 char const *cp
, *res
;
348 /* The order of evaluation is "%" and "#" expand into constants.
349 * "&" can expand into "+". "+" can expand into shell meta characters.
350 * Shell meta characters expand into constants.
351 * This way, we make no recursive expansion */
352 if ((fexpm
& FEXP_NSHORTCUT
) || (res
= shortcut_expand(name
)) == NULL
)
359 if (res
[1] == ':' && res
[2] != '\0') {
363 res
= _findmail((res
[1] != '\0' ? res
+ 1 : myname
), (res
[1] != '\0'));
368 if (prevfile
[0] == '\0') {
369 n_err(_("No previous file\n"));
377 res
= ok_vlook(MBOX
);
381 /* POSIX: if *folder* unset or null, "+" shall be retained */
382 if (*res
== '+' && *(cp
= folder_query()) != '\0') {
383 size_t i
= strlen(cp
);
385 res
= str_concat_csvl(&s
, cp
,
386 ((i
== 0 || cp
[i
-1] == '/') ? "" : "/"), res
+ 1, NULL
)->s
;
389 /* TODO *folder* can't start with %[:], can it!?! */
390 if (res
[0] == '%' && res
[1] == ':') {
396 /* Catch the most common shell meta character */
398 res
= n_shell_expand_tilde(res
, NULL
);
402 if (anyof(res
, "|&;<>{}()[]*?$`'\"\\")) {
405 if(fexpm
& FEXP_NOPROTO
)
407 else switch(which_protocol(res
)){
418 res
= (fexpm
& FEXP_NSHELL
) ? n_shell_expand_var(res
, TRU1
, NULL
)
419 : _globname(res
, fexpm
);
425 if (fexpm
& FEXP_LOCAL
)
426 switch (which_protocol(res
)) {
431 n_err(_("Not a local file or directory: \"%s\"\n"), name
);
444 fexpand_nshell_quote(char const *name
)
450 for (i
= j
= 0; (c
= name
[i
]) != '\0'; ++i
)
455 rv
= savestrbuf(name
, i
);
457 rv
= salloc(i
+ j
+1);
458 for (i
= j
= 0; (c
= name
[i
]) != '\0'; ++i
) {
470 n_shell_expand_tilde(char const *s
, bool_t
*err_or_null
)
484 if (*(rp
= s
+ 1) == '/' || *rp
== '\0')
487 if ((rp
= strchr(s
+ 1, '/')) == NULL
)
488 rp
= (np
= UNCONST(s
)) + 1;
490 nl
= PTR2SIZE(rp
- s
);
491 np
= savestrbuf(s
, nl
);
494 if ((pwp
= getpwnam(np
)) == NULL
) {
503 rv
= salloc(nl
+ 1 + rl
+1);
506 memcpy(rv
+ nl
, rp
, rl
);
515 if (err_or_null
!= NULL
)
522 n_shell_expand_var(char const *s
, bool_t bsescape
, bool_t
*err_or_null
)
524 struct shvar_stack top
;
528 memset(&top
, 0, sizeof top
);
531 if ((top
.shs_err
= err_or_null
) != NULL
)
533 top
.shs_bsesc
= bsescape
;
534 rv
= _sh_exp_var(&top
);
540 n_shell_expand_escape(char const **s
, bool_t use_nail_extensions
)/* TODO DROP!*/
548 if ((c
= *xs
& 0xFF) == '\0')
554 switch ((c
= *xs
& 0xFF)) {
555 case 'a': c
= '\a'; break;
556 case 'b': c
= '\b'; break;
557 case 'c': c
= PROMPT_STOP
; break;
558 case 'f': c
= '\f'; break;
559 case 'n': c
= '\n'; break;
560 case 'r': c
= '\r'; break;
561 case 't': c
= '\t'; break;
562 case 'v': c
= '\v'; break;
570 /* Hexadecimal TODO uses ASCII */
573 static ui8_t
const hexatoi
[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
576 hexatoi[(ui8_t)((n) - ((n) <= '9' ? 48 : ((n) <= 'F' ? 55 : 87)))]
584 if(options
& OPT_D_V
)
585 n_err(_("Invalid \"\\xNUMBER\" notation in \"%s\"\n"), xs
- 1);
599 /* octal, with optional 0 prefix */
609 for (c
= 0, n
= 3; n
-- > 0 && octalchar(*xs
); ++xs
) {
615 /* S-nail extension for nice (get)prompt(()) support */
620 if (use_nail_extensions
) {
622 case '&': c
= ok_blook(bsdcompat
) ? '&' : '?'; break;
623 case '?': c
= (pstate
& PS_EVAL_ERROR
) ? '1' : '0'; break;
624 case '$': c
= PROMPT_DOLLAR
; break;
625 case '@': c
= PROMPT_AT
; break;
632 /* A sole <backslash> at EOS is treated as-is! */
646 FL
enum n_shexp_state
647 n_shell_parse_token(struct n_string
*store
, struct str
*input
, bool_t dolog
){
649 bool_t skipq
, surplus
;
650 enum n_shexp_state rv
;
656 assert(store
!= NULL
);
657 assert(input
!= NULL
);
658 assert(input
->l
== 0 || input
->s
!= NULL
);
661 dolog
= ((options
& OPT_D_V
) != 0);
664 if((il
= input
->l
) == UIZ_MAX
)
665 input
->l
= il
= strlen(ib
);
667 store
= n_string_reserve(store
, MIN(il
, 32)); /* XXX */
669 for(rv
= n_SHEXP_STATE_NONE
, skipq
= surplus
= FAL0
, quotec
= '\0'; il
> 0;){
672 /* If no quote-mode active.. */
674 if(c
== '"' || c
== '\''){
676 surplus
= (c
== '"');
689 /* Outside of quotes this just escapes any next character, but a sole
690 * <backslash> at EOS is left unchanged */
694 rv
|= n_SHEXP_STATE_STOP
;
696 }else if(blankchar(c
))
701 skipq
= surplus
= FAL0
;
704 }else if(c
== '\\' && surplus
){
705 char const *ib_save
= ib
- 1;
707 /* A sole <backslash> at EOS is treated as-is! */
710 else if((c2
= *ib
) == quotec
){
713 }else if(quotec
== '"'){
715 * The <backslash> shall retain its special meaning as an
716 * escape character (see Section 2.2.1) only when followed
717 * by one of the following characters when considered
718 * special: $ ` " \ <newline> */
722 /* case '"': already handled via c2 == quotec */
731 /* Dollar-single-quote */
735 /* case '\'': already handled via c2 == quotec */
740 case 'b': c
= '\b'; break;
741 case 'f': c
= '\f'; break;
742 case 'n': c
= '\n'; break;
743 case 'r': c
= '\r'; break;
744 case 't': c
= '\t'; break;
745 case 'v': c
= '\v'; break;
748 case 'e': c
= '\033'; break;
750 /* Control character */
753 goto j_dollar_ungetc
;
757 c
= upperconv(c2
) ^ 0x40;
758 if((ui8_t
)c
> 0x1F && c
!= 0x7F){ /* ASCII C0: 0..1F, 7F */
760 n_err(_("Invalid \"\\c\" notation: \"%.*s\"\n"),
761 (int)input
->l
, input
->s
);
762 rv
|= n_SHEXP_STATE_ERR_CONTROL
;
764 /* As an implementation-defined extension, support \c@
765 * EQ printf(1) alike \c */
767 rv
|= n_SHEXP_STATE_STOP
;
772 /* Octal sequence: 1 to 3 octal bytes */
774 /* As an extension (dependent on where you look, echo(1), or
775 * awk(1)/tr(1) etc.), allow leading "0" octal indicator */
776 if(il
> 0 && (c
= *ib
) >= '0' && c
<= '7'){
781 case '1': case '2': case '3':
782 case '4': case '5': case '6': case '7':
784 if(il
> 0 && (c
= *ib
) >= '0' && c
<= '7'){
785 c2
= (c2
<< 3) | (c
- '0');
788 if(il
> 0 && (c
= *ib
) >= '0' && c
<= '7'){
789 if((ui8_t
)c2
> 0x1F){
791 n_err(_("\"\\0\" argument exceeds a byte: "
792 "\"%.*s\"\n"), (int)input
->l
, input
->s
);
793 rv
|= n_SHEXP_STATE_ERR_NUMBER
;
797 c2
= (c2
<< 3) | (c
-= '0');
806 /* ISO 10646 / Unicode sequence, 8 or 4 hexadecimal bytes */
815 goto j_dollar_ungetc
;
819 /* Hexadecimal sequence, 1 or 2 hexadecimal bytes */
823 goto j_dollar_ungetc
;
827 static ui8_t
const hexatoi
[] = { /* XXX uses ASCII */
828 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
833 for(no
= j
= 0; i
-- > 0; --il
, ++ib
, ++j
){
837 no
+= hexatoi
[(ui8_t
)((c
) - ((c
) <= '9' ? 48
838 : ((c
) <= 'F' ? 55 : 87)))];
842 c2
= (c2
== 'U' || c2
== 'u') ? 'u' : 'x';
844 n_err(_("Invalid \"\\%c\" notation: \"%.*s\"\n"),
845 c2
, (int)input
->l
, input
->s
);
846 rv
|= n_SHEXP_STATE_ERR_NUMBER
;
852 /* Unicode massage */
853 if(c2
!= 'U' && c2
!= 'u'){
854 if((c
= (char)no
) == '\0')
859 store
= n_string_reserve(store
, MAX(j
, 4));
862 if(no
> 0x10FFFF){ /* XXX magic; CText */
864 n_err(_("\"\\U\" argument exceeds 0x10FFFF: "
865 "\"%.*s\"\n"), (int)input
->l
, input
->s
);
866 rv
|= n_SHEXP_STATE_UNICODE
|
867 n_SHEXP_STATE_ERR_NUMBER
|
868 n_SHEXP_STATE_ERR_UNICODE
;
870 }else if((options
& OPT_UNICODE
) ||
871 (c2
= n_uasciichar(no
))){
875 rv
|= n_SHEXP_STATE_UNICODE
;
876 j
= n_utf32_to_utf8(no
, utf
);
877 store
= n_string_push_buf(store
, utf
, j
);
879 /* Write unchanged */
881 rv
|= n_SHEXP_STATE_UNICODE
|
882 n_SHEXP_STATE_ERR_UNICODE
;
884 store
= n_string_push_buf(store
, ib_save
,
885 PTR2SIZE(ib
- ib_save
));
888 if(n_uasciichar(no
) && cntrlchar(no
)) /* TODO ctext */
889 rv
|= n_SHEXP_STATE_CONTROL
;
897 /* Extension: \$ can be used to expand a variable.
898 * Bug|ad effect: if conversion fails, not written "as-is" */
901 goto j_dollar_ungetc
;
906 /* Follow bash behaviour, print sequence unchanged */
911 }else if(c
== '$' && quotec
== '"' && il
> 0) J_var_expand
:{
914 if(!(brace
= (*ib
== '{')) || il
> 1){
920 for(i
= 0; il
> 0 && (c
= *ib
, a_SHEXP_ISVARC(c
)); ++i
)
924 if(il
== 0 || *ib
!= '}'){
926 assert(surplus
&& quotec
== '\'');
930 n_err(_("Closing brace missing for ${VAR}: \"%.*s\"\n"),
931 (int)input
->l
, input
->s
);
932 rv
|= n_SHEXP_STATE_STOP
|
933 n_SHEXP_STATE_ERR_QUOTEOPEN
| n_SHEXP_STATE_ERR_BRACE
;
945 n_err(_("Bad substitution (${}): \"%.*s\"\n"),
946 (int)input
->l
, input
->s
);
947 rv
|= n_SHEXP_STATE_STOP
| n_SHEXP_STATE_ERR_BADSUB
;
952 vp
= savestrbuf(vp
, i
);
953 /* Check getenv(3) shall no internal variable exist! */
954 if((cp
= vok_vlook(vp
)) != NULL
|| (cp
= getenv(vp
)) != NULL
){
955 store
= n_string_push_cp(store
, cp
);
956 for(; (c
= *cp
) != '\0'; ++cp
)
958 rv
|= n_SHEXP_STATE_CONTROL
;
965 }else if(c
== '`' && quotec
== '"' && il
> 0){ /* TODO shell command */
972 rv
|= n_SHEXP_STATE_CONTROL
;
973 store
= n_string_push_c(store
, c
);
979 n_err(_("Missing closing quote in: \"%.*s\"\n"),
980 (int)input
->l
, input
->s
);
981 rv
|= n_SHEXP_STATE_ERR_QUOTEOPEN
;
984 if(rv
& n_SHEXP_STATE_CONTROL
)
985 pstate
|= PS_WYSHLIST_SAW_CONTROL
;
986 input
->s
= UNCONST(ib
);
993 n_shell_quote(struct n_string
*store
, struct str
const *input
){
994 /* TODO In v15 we need to save (possibly normalize) away user input,
995 * TODO so that the ORIGINAL (normalized) input can be used directly.
996 * Because we're the last, stay primitive */
1002 assert(store
!= NULL
);
1003 assert(input
!= NULL
);
1004 assert(input
->l
== 0 || input
->s
!= NULL
);
1007 if((il
= input
->l
) == UIZ_MAX
)
1010 /* Calculate necessary buffer space */
1012 qflag
= TRU1
, j
= 0;
1013 else for(qflag
= FAL0
, j
= sizeof("''") -1, i
= 0; i
< il
; ++i
){
1016 if(c
== '\'' || !asciichar(c
) || cntrlchar(c
)){
1018 j
+= sizeof("\\0377") -1;
1019 }else if(c
== '\\' || c
== '$' || blankchar(c
)){
1021 j
+= sizeof("\\ ") -1;
1025 store
= n_string_reserve(store
, j
+ 3);
1028 store
= n_string_push_buf(store
, ib
, il
);
1029 else if(qflag
== TRU1
){
1030 store
= n_string_push_c(store
, '\'');
1031 store
= n_string_push_buf(store
, ib
, il
);
1032 store
= n_string_push_c(store
, '\'');
1034 store
= n_string_push_buf(store
, "$'", sizeof("$'") -1);
1036 for(qflag
= FAL0
, j
= 0, i
= 0; i
< il
; ++i
){
1039 if(c
== '\'' || !asciichar(c
) || cntrlchar(c
)){
1040 store
= n_string_push_c(store
, '\\');
1045 case 0x07: c
= 'a'; break;
1046 case 0x08: c
= 'b'; break;
1047 case 0x09: c
= 't'; break;
1048 case 0x0A: c
= 'n'; break;
1049 case 0x0B: c
= 'v'; break;
1050 case 0x0C: c
= 'f'; break;
1051 case 0x0D: c
= 'r'; break;
1055 store
= n_string_push_c(store
, 'c');
1058 store
= n_string_push_c(store
, c
);
1060 }else if(c
!= '\''){
1061 store
= n_string_push_buf(store
, "xFF", sizeof("xFF") -1);
1062 n_c_to_hex_base16(&store
->s_dat
[store
->s_len
- 2], c
);
1066 store
= n_string_push_c(store
, c
);
1068 store
= n_string_push_c(store
, '\'');
1075 n_shell_quote_cp(char const *cp
){
1076 struct n_string store
;
1083 input
.s
= UNCONST(cp
);
1085 rv
= n_string_cp(n_shell_quote(n_string_creat_auto(&store
), &input
));
1086 n_string_gut(n_string_drop_ownership(&store
));