diff options
Diffstat (limited to 'contrib/nvi/ex/ex_argv.c')
-rw-r--r-- | contrib/nvi/ex/ex_argv.c | 464 |
1 files changed, 311 insertions, 153 deletions
diff --git a/contrib/nvi/ex/ex_argv.c b/contrib/nvi/ex/ex_argv.c index cc5a201..b3e3edf 100644 --- a/contrib/nvi/ex/ex_argv.c +++ b/contrib/nvi/ex/ex_argv.c @@ -10,17 +10,19 @@ #include "config.h" #ifndef lint -static const char sccsid[] = "@(#)ex_argv.c 10.26 (Berkeley) 9/20/96"; +static const char sccsid[] = "$Id: ex_argv.c,v 11.2 2012/10/09 23:00:29 zy Exp $"; #endif /* not lint */ #include <sys/types.h> #include <sys/queue.h> +#include <sys/time.h> #include <bitstring.h> #include <ctype.h> #include <dirent.h> #include <errno.h> #include <limits.h> +#include <pwd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -31,9 +33,9 @@ static const char sccsid[] = "@(#)ex_argv.c 10.26 (Berkeley) 9/20/96"; static int argv_alloc __P((SCR *, size_t)); static int argv_comp __P((const void *, const void *)); static int argv_fexp __P((SCR *, EXCMD *, - char *, size_t, char *, size_t *, char **, size_t *, int)); -static int argv_lexp __P((SCR *, EXCMD *, char *)); -static int argv_sexp __P((SCR *, char **, size_t *, size_t *)); + CHAR_T *, size_t, CHAR_T *, size_t *, CHAR_T **, size_t *, int)); +static int argv_sexp __P((SCR *, CHAR_T **, size_t *, size_t *)); +static int argv_flt_user __P((SCR *, EXCMD *, CHAR_T *, size_t)); /* * argv_init -- @@ -42,9 +44,7 @@ static int argv_sexp __P((SCR *, char **, size_t *, size_t *)); * PUBLIC: int argv_init __P((SCR *, EXCMD *)); */ int -argv_init(sp, excp) - SCR *sp; - EXCMD *excp; +argv_init(SCR *sp, EXCMD *excp) { EX_PRIVATE *exp; @@ -61,20 +61,16 @@ argv_init(sp, excp) * argv_exp0 -- * Append a string to the argument list. * - * PUBLIC: int argv_exp0 __P((SCR *, EXCMD *, char *, size_t)); + * PUBLIC: int argv_exp0 __P((SCR *, EXCMD *, CHAR_T *, size_t)); */ int -argv_exp0(sp, excp, cmd, cmdlen) - SCR *sp; - EXCMD *excp; - char *cmd; - size_t cmdlen; +argv_exp0(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { EX_PRIVATE *exp; exp = EXP(sp); argv_alloc(sp, cmdlen); - memcpy(exp->args[exp->argsoff]->bp, cmd, cmdlen); + MEMCPY(exp->args[exp->argsoff]->bp, cmd, cmdlen); exp->args[exp->argsoff]->bp[cmdlen] = '\0'; exp->args[exp->argsoff]->len = cmdlen; ++exp->argsoff; @@ -88,33 +84,28 @@ argv_exp0(sp, excp, cmd, cmdlen) * Do file name expansion on a string, and append it to the * argument list. * - * PUBLIC: int argv_exp1 __P((SCR *, EXCMD *, char *, size_t, int)); + * PUBLIC: int argv_exp1 __P((SCR *, EXCMD *, CHAR_T *, size_t, int)); */ int -argv_exp1(sp, excp, cmd, cmdlen, is_bang) - SCR *sp; - EXCMD *excp; - char *cmd; - size_t cmdlen; - int is_bang; +argv_exp1(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, int is_bang) { EX_PRIVATE *exp; size_t blen, len; - char *bp, *p, *t; + CHAR_T *p, *t, *bp; - GET_SPACE_RET(sp, bp, blen, 512); + GET_SPACE_RETW(sp, bp, blen, 512); len = 0; exp = EXP(sp); if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) { - FREE_SPACE(sp, bp, blen); + FREE_SPACEW(sp, bp, blen); return (1); } /* If it's empty, we're done. */ if (len != 0) { for (p = bp, t = bp + len; p < t; ++p) - if (!isblank(*p)) + if (!cmdskip(*p)) break; if (p == t) goto ret; @@ -123,7 +114,7 @@ argv_exp1(sp, excp, cmd, cmdlen, is_bang) (void)argv_exp0(sp, excp, bp, len); -ret: FREE_SPACE(sp, bp, blen); +ret: FREE_SPACEW(sp, bp, blen); return (0); } @@ -132,24 +123,20 @@ ret: FREE_SPACE(sp, bp, blen); * Do file name and shell expansion on a string, and append it to * the argument list. * - * PUBLIC: int argv_exp2 __P((SCR *, EXCMD *, char *, size_t)); + * PUBLIC: int argv_exp2 __P((SCR *, EXCMD *, CHAR_T *, size_t)); */ int -argv_exp2(sp, excp, cmd, cmdlen) - SCR *sp; - EXCMD *excp; - char *cmd; - size_t cmdlen; +argv_exp2(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { size_t blen, len, n; int rval; - char *bp, *mp, *p; + CHAR_T *bp, *p; - GET_SPACE_RET(sp, bp, blen, 512); + GET_SPACE_RETW(sp, bp, blen, 512); -#define SHELLECHO "echo " -#define SHELLOFFSET (sizeof(SHELLECHO) - 1) - memcpy(bp, SHELLECHO, SHELLOFFSET); +#define SHELLECHO L("echo ") +#define SHELLOFFSET (SIZE(SHELLECHO) - 1) + MEMCPY(bp, SHELLECHO, SHELLOFFSET); p = bp + SHELLOFFSET; len = SHELLOFFSET; @@ -175,28 +162,15 @@ argv_exp2(sp, excp, cmd, cmdlen) * but then, if your shell was csh, the above example will behave * differently in nvi than in vi. If you want to get other characters * passed through to your shell, change the "meta" option. - * - * To avoid a function call per character, we do a first pass through - * the meta characters looking for characters that aren't expected - * to be there, and then we can ignore them in the user's argument. */ if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1)) n = 0; else { - for (p = mp = O_STR(sp, O_SHELLMETA); *p != '\0'; ++p) - if (isblank(*p) || isalnum(*p)) - break; p = bp + SHELLOFFSET; n = len - SHELLOFFSET; - if (*p != '\0') { - for (; n > 0; --n, ++p) - if (strchr(mp, *p) != NULL) - break; - } else - for (; n > 0; --n, ++p) - if (!isblank(*p) && - !isalnum(*p) && strchr(mp, *p) != NULL) - break; + for (; n > 0; --n, ++p) + if (IS_SHELLMETA(sp, *p)) + break; } /* @@ -204,13 +178,11 @@ argv_exp2(sp, excp, cmd, cmdlen) * it. Unfortunately, this is comparatively slow. Historically, it * didn't matter much, since users don't enter meta characters as part * of pathnames that frequently. The addition of filename completion - * broke that assumption because it's easy to use. As a result, lots - * folks have complained that the expansion code is too slow. So, we - * detect filename completion as a special case, and do it internally. - * Note that this code assumes that the <asterisk> character is the - * match-anything meta character. That feels safe -- if anyone writes - * a shell that doesn't follow that convention, I'd suggest giving them - * a festive hot-lead enema. + * broke that assumption because it's easy to use. To increase the + * completion performance, nvi used to have an internal routine to + * handle "filename*". However, the shell special characters does not + * limit to "shellmeta", so such a hack breaks historic practice. + * After it all, we split the completion logic out from here. */ switch (n) { case 0: @@ -218,13 +190,6 @@ argv_exp2(sp, excp, cmd, cmdlen) len -= SHELLOFFSET; rval = argv_exp3(sp, excp, p, len); break; - case 1: - if (*p == '*') { - *p = '\0'; - rval = argv_lexp(sp, excp, bp + SHELLOFFSET); - break; - } - /* FALLTHROUGH */ default: if (argv_sexp(sp, &bp, &blen, &len)) { rval = 1; @@ -235,7 +200,7 @@ argv_exp2(sp, excp, cmd, cmdlen) break; } -err: FREE_SPACE(sp, bp, blen); +err: FREE_SPACEW(sp, bp, blen); return (rval); } @@ -244,25 +209,21 @@ err: FREE_SPACE(sp, bp, blen); * Take a string and break it up into an argv, which is appended * to the argument list. * - * PUBLIC: int argv_exp3 __P((SCR *, EXCMD *, char *, size_t)); + * PUBLIC: int argv_exp3 __P((SCR *, EXCMD *, CHAR_T *, size_t)); */ int -argv_exp3(sp, excp, cmd, cmdlen) - SCR *sp; - EXCMD *excp; - char *cmd; - size_t cmdlen; +argv_exp3(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) { EX_PRIVATE *exp; size_t len; int ch, off; - char *ap, *p; + CHAR_T *ap, *p; for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) { /* Skip any leading whitespace. */ for (; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; - if (!isblank(ch)) + if (!cmdskip(ch)) break; } if (cmdlen == 0) @@ -282,7 +243,7 @@ argv_exp3(sp, excp, cmd, cmdlen) if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) { ++cmd; --cmdlen; - } else if (isblank(ch)) + } else if (cmdskip(ch)) break; } @@ -312,20 +273,99 @@ argv_exp3(sp, excp, cmd, cmdlen) } /* + * argv_flt_ex -- + * Filter the ex commands with a prefix, and append the results to + * the argument list. + * + * PUBLIC: int argv_flt_ex __P((SCR *, EXCMD *, CHAR_T *, size_t)); + */ +int +argv_flt_ex(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) +{ + EX_PRIVATE *exp; + EXCMDLIST const *cp; + int off; + size_t len; + + exp = EXP(sp); + + for (off = exp->argsoff, cp = cmds; cp->name != NULL; ++cp) { + len = STRLEN(cp->name); + if (cmdlen > 0 && + (cmdlen > len || MEMCMP(cmd, cp->name, cmdlen))) + continue; + + /* Copy the matched ex command name. */ + argv_alloc(sp, len + 1); + MEMCPY(exp->args[exp->argsoff]->bp, cp->name, len + 1); + exp->args[exp->argsoff]->len = len; + ++exp->argsoff; + excp->argv = exp->args; + excp->argc = exp->argsoff; + } + + return (0); +} + +/* + * argv_flt_user -- + * Filter the ~user list on the system with a prefix, and append + * the results to the argument list. + */ +static int +argv_flt_user(SCR *sp, EXCMD *excp, CHAR_T *uname, size_t ulen) +{ + EX_PRIVATE *exp; + struct passwd *pw; + int off; + char *np; + size_t len, nlen; + + exp = EXP(sp); + off = exp->argsoff; + + /* The input must come with a leading '~'. */ + INT2CHAR(sp, uname + 1, ulen - 1, np, nlen); + if ((np = v_strdup(sp, np, nlen)) == NULL) + return (1); + + setpwent(); + while ((pw = getpwent()) != NULL) { + len = strlen(pw->pw_name); + if (nlen > 0 && + (nlen > len || memcmp(np, pw->pw_name, nlen))) + continue; + + /* Copy '~' + the matched user name. */ + CHAR2INT(sp, pw->pw_name, len + 1, uname, ulen); + argv_alloc(sp, ulen + 1); + exp->args[exp->argsoff]->bp[0] = '~'; + MEMCPY(exp->args[exp->argsoff]->bp + 1, uname, ulen); + exp->args[exp->argsoff]->len = ulen; + ++exp->argsoff; + excp->argv = exp->args; + excp->argc = exp->argsoff; + } + endpwent(); + free(np); + + qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); + return (0); +} + +/* * argv_fexp -- * Do file name and bang command expansion. */ static int -argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang) - SCR *sp; - EXCMD *excp; - char *cmd, *p, **bpp; - size_t cmdlen, *lenp, *blenp; - int is_bang; +argv_fexp(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, CHAR_T *p, size_t *lenp, CHAR_T **bpp, size_t *blenp, int is_bang) { EX_PRIVATE *exp; - char *bp, *t; + char *t; size_t blen, len, off, tlen; + CHAR_T *bp; + CHAR_T *wp; + size_t wlen; /* Replace file name characters. */ for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd) @@ -339,11 +379,11 @@ argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang) "115|No previous command to replace \"!\""); return (1); } - len += tlen = strlen(exp->lastbcomm); + len += tlen = STRLEN(exp->lastbcomm); off = p - bp; - ADD_SPACE_RET(sp, bp, blen, len); + ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; - memcpy(p, exp->lastbcomm, tlen); + MEMCPY(p, exp->lastbcomm, tlen); p += tlen; F_SET(excp, E_MODIFY); break; @@ -356,10 +396,11 @@ argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang) tlen = strlen(t); len += tlen; off = p - bp; - ADD_SPACE_RET(sp, bp, blen, len); + ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; - memcpy(p, t, tlen); - p += tlen; + CHAR2INT(sp, t, tlen, wp, wlen); + MEMCPY(p, wp, wlen); + p += wlen; F_SET(excp, E_MODIFY); break; case '#': @@ -370,10 +411,11 @@ argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang) } len += tlen = strlen(t); off = p - bp; - ADD_SPACE_RET(sp, bp, blen, len); + ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; - memcpy(p, t, tlen); - p += tlen; + CHAR2INT(sp, t, tlen, wp, wlen); + MEMCPY(p, wp, wlen); + p += wlen; F_SET(excp, E_MODIFY); break; case '\\': @@ -392,7 +434,7 @@ argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang) default: ins_ch: ++len; off = p - bp; - ADD_SPACE_RET(sp, bp, blen, len); + ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; *p++ = *cmd; } @@ -400,7 +442,7 @@ ins_ch: ++len; /* Nul termination. */ ++len; off = p - bp; - ADD_SPACE_RET(sp, bp, blen, len); + ADD_SPACE_RETW(sp, bp, blen, len); p = bp + off; *p = '\0'; @@ -416,9 +458,7 @@ ins_ch: ++len; * Make more space for arguments. */ static int -argv_alloc(sp, len) - SCR *sp; - size_t len; +argv_alloc(SCR *sp, size_t len) { ARGS *ap; EX_PRIVATE *exp; @@ -483,8 +523,7 @@ mem: msgq(sp, M_SYSERR, NULL); * PUBLIC: int argv_free __P((SCR *)); */ int -argv_free(sp) - SCR *sp; +argv_free(SCR *sp) { EX_PRIVATE *exp; int off; @@ -507,89 +546,103 @@ argv_free(sp) } /* - * argv_lexp -- + * argv_flt_path -- * Find all file names matching the prefix and append them to the - * buffer. + * argument list. + * + * PUBLIC: int argv_flt_path __P((SCR *, EXCMD *, CHAR_T *, size_t)); */ -static int -argv_lexp(sp, excp, path) - SCR *sp; - EXCMD *excp; - char *path; +int +argv_flt_path(SCR *sp, EXCMD *excp, CHAR_T *path, size_t plen) { struct dirent *dp; DIR *dirp; EX_PRIVATE *exp; int off; size_t dlen, len, nlen; - char *dname, *name, *p; + CHAR_T *dname; + CHAR_T *p, *np, *n; + char *name, *tp, *epd = NULL; + CHAR_T *wp; + size_t wlen; exp = EXP(sp); /* Set up the name and length for comparison. */ - if ((p = strrchr(path, '/')) == NULL) { - dname = "."; + if ((path = v_wstrdup(sp, path, plen)) == NULL) + return (1); + if ((p = STRRCHR(path, '/')) == NULL) { + if (*path == '~') { + int rc; + + /* Filter ~user list instead. */ + rc = argv_flt_user(sp, excp, path, plen); + free(path); + return (rc); + } + dname = L("."); dlen = 0; - name = path; - } else { + np = path; + } else { if (p == path) { - dname = "/"; + dname = L("/"); dlen = 1; } else { *p = '\0'; dname = path; - dlen = strlen(path); + dlen = p - path; } - name = p + 1; + np = p + 1; } - nlen = strlen(name); - /* - * XXX - * We don't use the d_namlen field, it's not portable enough; we - * assume that d_name is nul terminated, instead. - */ - if ((dirp = opendir(dname)) == NULL) { - msgq_str(sp, M_SYSERR, dname, "%s"); + INT2CHAR(sp, dname, dlen + 1, tp, nlen); + if ((epd = expanduser(tp)) != NULL) + tp = epd; + if ((dirp = opendir(tp)) == NULL) { + free(epd); + free(path); return (1); } + free(epd); + + INT2CHAR(sp, np, STRLEN(np), tp, nlen); + if ((name = v_strdup(sp, tp, nlen)) == NULL) { + free(path); + return (1); + } + for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) { if (nlen == 0) { if (dp->d_name[0] == '.') continue; - len = strlen(dp->d_name); + len = dp->d_namlen; } else { - len = strlen(dp->d_name); + len = dp->d_namlen; if (len < nlen || memcmp(dp->d_name, name, nlen)) continue; } /* Directory + name + slash + null. */ - argv_alloc(sp, dlen + len + 2); - p = exp->args[exp->argsoff]->bp; + CHAR2INT(sp, dp->d_name, len + 1, wp, wlen); + argv_alloc(sp, dlen + wlen + 1); + n = exp->args[exp->argsoff]->bp; if (dlen != 0) { - memcpy(p, dname, dlen); - p += dlen; + MEMCPY(n, dname, dlen); + n += dlen; if (dlen > 1 || dname[0] != '/') - *p++ = '/'; + *n++ = '/'; + exp->args[exp->argsoff]->len = dlen + 1; } - memcpy(p, dp->d_name, len + 1); - exp->args[exp->argsoff]->len = dlen + len + 1; + MEMCPY(n, wp, wlen); + exp->args[exp->argsoff]->len += wlen - 1; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; } closedir(dirp); + free(name); + free(path); - if (off == exp->argsoff) { - /* - * If we didn't find a match, complain that the expansion - * failed. We can't know for certain that's the error, but - * it's a good guess, and it matches historic practice. - */ - msgq(sp, M_ERR, "304|Shell expansion failed"); - return (1); - } qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); return (0); } @@ -599,10 +652,9 @@ argv_lexp(sp, excp, path) * Alphabetic comparison. */ static int -argv_comp(a, b) - const void *a, *b; +argv_comp(const void *a, const void *b) { - return (strcmp((char *)(*(ARGS **)a)->bp, (char *)(*(ARGS **)b)->bp)); + return (STRCMP((*(ARGS **)a)->bp, (*(ARGS **)b)->bp)); } /* @@ -611,17 +663,17 @@ argv_comp(a, b) * a buffer. */ static int -argv_sexp(sp, bpp, blenp, lenp) - SCR *sp; - char **bpp; - size_t *blenp, *lenp; +argv_sexp(SCR *sp, CHAR_T **bpp, size_t *blenp, size_t *lenp) { enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval; FILE *ifp; pid_t pid; size_t blen, len; int ch, std_output[2]; - char *bp, *p, *sh, *sh_path; + CHAR_T *bp, *p; + char *sh, *sh_path; + char *np; + size_t nlen; /* Secure means no shell access. */ if (O_ISSET(sp, O_SECURE)) { @@ -689,7 +741,8 @@ err: if (ifp != NULL) * XXX * Assume that all shells have -c. */ - execl(sh_path, sh, "-c", bp, NULL); + INT2CHAR(sp, bp, STRLEN(bp)+1, np, nlen); + execl(sh_path, sh, "-c", np, (char *)NULL); msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s"); _exit(127); default: /* Parent. */ @@ -707,9 +760,9 @@ err: if (ifp != NULL) * shell that does that is broken. */ for (p = bp, len = 0, ch = EOF; - (ch = getc(ifp)) != EOF; *p++ = ch, --blen, ++len) + (ch = GETC(ifp)) != EOF; *p++ = ch, blen-=sizeof(CHAR_T), ++len) if (blen < 5) { - ADD_SPACE_GOTO(sp, bp, *blenp, *blenp * 2); + ADD_SPACE_GOTOW(sp, bp, *blenp, *blenp * 2); p = bp + len; blen = *blenp - len; } @@ -744,7 +797,7 @@ alloc_err: rval = SEXP_ERR; rval = SEXP_EXPANSION_ERR; for (p = bp; len; ++p, --len) - if (!isblank(*p)) + if (!cmdskip(*p)) break; if (len == 0) rval = SEXP_EXPANSION_ERR; @@ -754,3 +807,108 @@ alloc_err: rval = SEXP_ERR; return (rval == SEXP_OK ? 0 : 1); } + +/* + * argv_esc -- + * Escape a string into an ex and shell argument. + * + * PUBLIC: CHAR_T *argv_esc __P((SCR *, EXCMD *, CHAR_T *, size_t)); + */ +CHAR_T * +argv_esc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len) +{ + size_t blen, off; + CHAR_T *bp, *p; + int ch; + + GET_SPACE_GOTOW(sp, bp, blen, len + 1); + + /* + * Leaving the first '~' unescaped causes the user to need a + * "./" prefix to edit a file which really starts with a '~'. + * However, the file completion happens to not work for these + * files without the prefix. + * + * All ex expansion characters, "!%#", are double escaped. + */ + for (p = bp; len > 0; ++str, --len) { + ch = *str; + off = p - bp; + if (blen / sizeof(CHAR_T) - off < 3) { + ADD_SPACE_GOTOW(sp, bp, blen, off + 3); + p = bp + off; + } + if (cmdskip(ch) || ch == '\n' || + IS_ESCAPE(sp, excp, ch)) /* Ex. */ + *p++ = CH_LITERAL; + else switch (ch) { + case '~': /* ~user. */ + if (p != bp) + *p++ = '\\'; + break; + case '+': /* Ex +cmd. */ + if (p == bp) + *p++ = '\\'; + break; + case '!': case '%': case '#': /* Ex exp. */ + *p++ = '\\'; + *p++ = '\\'; + break; + case ',': case '-': case '.': case '/': /* Safe. */ + case ':': case '=': case '@': case '_': + break; + default: /* Unsafe. */ + if (isascii(ch) && !isalnum(ch)) + *p++ = '\\'; + } + *p++ = ch; + } + *p = '\0'; + + return bp; + +alloc_err: + return NULL; +} + +/* + * argv_uesc -- + * Unescape an escaped ex and shell argument. + * + * PUBLIC: CHAR_T *argv_uesc __P((SCR *, EXCMD *, CHAR_T *, size_t)); + */ +CHAR_T * +argv_uesc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len) +{ + size_t blen; + CHAR_T *bp, *p; + + GET_SPACE_GOTOW(sp, bp, blen, len + 1); + + for (p = bp; len > 0; ++str, --len) { + if (IS_ESCAPE(sp, excp, *str)) { + if (--len < 1) + break; + ++str; + } else if (*str == '\\') { + if (--len < 1) + break; + ++str; + + /* Check for double escaping. */ + if (*str == '\\' && len > 1) + switch (str[1]) { + case '!': case '%': case '#': + ++str; + --len; + } + } + *p++ = *str; + } + *p = '\0'; + + return bp; + +alloc_err: + return NULL; +} |