summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsebres <sebres@users.sourceforge.net>2018-08-21 18:52:21 (GMT)
committersebres <sebres@users.sourceforge.net>2018-08-21 18:52:21 (GMT)
commitfb0bf9d1b9dbcac3d58bef9c86b6353eaf3d0c1e (patch)
tree6c74a46c1ec372017f5e0cf75a23763badcbfa38
parent01dd016ad89794ac85bfc2a7ccbedbb6d4c7f14f (diff)
downloadtcl-fb0bf9d1b9dbcac3d58bef9c86b6353eaf3d0c1e.zip
tcl-fb0bf9d1b9dbcac3d58bef9c86b6353eaf3d0c1e.tar.gz
tcl-fb0bf9d1b9dbcac3d58bef9c86b6353eaf3d0c1e.tar.bz2
fixes escape for special cases (+ more test-cases):
- `%` char to be escaped (quoted) in any case (regardless pairing flag), otherwise `%username%` will be interpolated as username. - escape of multiple backslashes before quote is different (as without following quote) in unpaired quote syntax (upaired flag set)
-rw-r--r--tests/winPipe.test30
-rw-r--r--win/tclWinPipe.c142
2 files changed, 139 insertions, 33 deletions
diff --git a/tests/winPipe.test b/tests/winPipe.test
index e817e85..c375d8f 100644
--- a/tests/winPipe.test
+++ b/tests/winPipe.test
@@ -332,7 +332,9 @@ proc _testExecArgs {single args} {
set broken {}
foreach args $args {
if {$single & 1} {
- set args [list $args]
+ # enclose single test-arg between 1st/3rd to be sure nothing is truncated
+ # (e. g. to cover unexpected trim by nts-zero case, and args don't recombined):
+ set args [list "1st" $args "3rd"]
}
foreach cmd $cmds {
set e [list [file tail $path(echoArgs.tcl)] {*}$args]
@@ -414,8 +416,15 @@ set injectList {
"test\"whoami\\" "test\"\"whoami\\"
"test\"\"\"whoami\\" "test\"\"\"\"whoami\\"
+ {test\\&\\test} {test"\\&\\test}
+ {"test\\&\\test} {"test"\\&\\"test"}
+ {test\\"&"\\test} {test"\\"&"\\test}
+ {"test\\"&"\\test} {"test"\\"&"\\"test"}
+
{test\"&whoami} {test"\"&whoami}
{test""\"&whoami} {test"""\"&whoami}
+ {test\"\&whoami} {test"\"\&whoami}
+ {test""\"\&whoami} {test"""\"\&whoami}
{test&whoami} {test|whoami}
{"test&whoami} {"test|whoami}
@@ -445,6 +454,25 @@ set injectList {
{test^whoami} {test^^echo ^^^}
{test"^whoami} {test"^^echo ^^^}
{test"^echo ^^^"} {test""^echo" ^^^"}
+
+ {test%USERDOMAIN%\%USERNAME%}
+ {test" %USERDOMAIN%\%USERNAME%}
+ {test%USERDOMAIN%\\%USERNAME%}
+ {test" %USERDOMAIN%\\%USERNAME%}
+ {test%USERDOMAIN%&%USERNAME%}
+ {test" %USERDOMAIN%&%USERNAME%}
+ {test%USERDOMAIN%\&\%USERNAME%}
+ {test" %USERDOMAIN%\&\%USERNAME%}
+
+ {test%USERDOMAIN%\&\test}
+ {test" %USERDOMAIN%\&\test}
+ {test%USERDOMAIN%\\&\\test}
+ {test" %USERDOMAIN%\\&\\test}
+
+ {test%USERDOMAIN%\&\"test}
+ {test" %USERDOMAIN%\&\"test}
+ {test%USERDOMAIN%\\&\\"test}
+ {test" %USERDOMAIN%\\&\\"test}
}
### validate the pass-thru from BuildCommandLine() to the crt's parse_cmdline().
diff --git a/win/tclWinPipe.c b/win/tclWinPipe.c
index a6a8f22..728e43a 100644
--- a/win/tclWinPipe.c
+++ b/win/tclWinPipe.c
@@ -1527,6 +1527,86 @@ ApplicationType(
*----------------------------------------------------------------------
*/
+static const char *
+BuildCmdLineBypassBS(
+ const char *current,
+ const char **bspos
+) {
+ /* mark first backslash possition */
+ if (!*bspos) {
+ *bspos = current;
+ }
+ do {
+ current++;
+ } while (*current == '\\');
+ return current;
+}
+
+static void
+QuoteCmdLineBackslash(
+ Tcl_DString *dsPtr,
+ const char *start,
+ const char *current,
+ const char *bspos
+) {
+ if (!bspos) {
+ if (current > start) { /* part before current (special) */
+ Tcl_DStringAppend(dsPtr, start, (int) (current - start));
+ }
+ } else {
+ if (bspos > start) { /* part before first backslash */
+ Tcl_DStringAppend(dsPtr, start, (int) (bspos - start));
+ }
+ while (bspos++ < current) { /* each backslash twice */
+ Tcl_DStringAppend(dsPtr, "\\\\", 2);
+ }
+ }
+}
+
+static const char *
+QuoteCmdLinePart(
+ Tcl_DString *dsPtr,
+ const char *start,
+ const char *special,
+ const char *specMetaChars,
+ const char **bspos
+) {
+ if (!*bspos) {
+ /* rest before special (before quote) */
+ QuoteCmdLineBackslash(dsPtr, start, special, NULL);
+ start = special;
+ } else {
+ /* rest before first backslash and backslashes into new quoted block */
+ QuoteCmdLineBackslash(dsPtr, start, *bspos, NULL);
+ start = *bspos;
+ }
+ /*
+ * escape all special chars enclosed in quotes like `"..."`, note that here we
+ * don't must escape `\` (with `\`), because it's outside of the main quotes,
+ * so `\` remains `\`, but important - not at end of part, because results as
+ * before the quote, so `%\%\` should be escaped as `"%\%"\\`).
+ */
+ Tcl_DStringAppend(dsPtr, "\"", 1); /* opening escape quote-char */
+ do {
+ *bspos = NULL;
+ special++;
+ if (*special == '\\') {
+ /* bypass backslashes (and mark first backslash possition)*/
+ special = BuildCmdLineBypassBS(special, bspos);
+ if (*special == '\0') break;
+ }
+ } while (*special && strchr(specMetaChars, *special));
+ if (!*bspos) {
+ /* unescaped rest before quote */
+ QuoteCmdLineBackslash(dsPtr, start, special, NULL);
+ } else {
+ /* unescaped rest before first backslash (rather belongs to the main block) */
+ QuoteCmdLineBackslash(dsPtr, start, *bspos, NULL);
+ }
+ Tcl_DStringAppend(dsPtr, "\"", 1); /* closing escape quote-char */
+ return special;
+}
+
static void
BuildCommandLine(
const char *executable, /* Full path of executable (including
@@ -1536,11 +1616,14 @@ BuildCommandLine(
Tcl_DString *linePtr) /* Initialized Tcl_DString that receives the
* command line (TCHAR). */
{
- const char *arg, *start, *special;
+ const char *arg, *start, *special, *bspos;
int quote = 0, i;
Tcl_DString ds;
- const static char *specMetaChars = "&|^<>!%()";
+ /* characters to enclose in quotes if unpaired quote flag set */
+ const static char *specMetaChars = "&|^<>!()%";
+ /* characters to enclose in quotes in any case (regardless unpaired-flag) */
+ const static char *specMetaChars2 = "%";
Tcl_DStringInit(&ds);
@@ -1566,6 +1649,7 @@ BuildCommandLine(
* 2 - previous arguments chain contains unpaired quote-char;
*/
quote &= ~1; /* reset escape flag */
+ bspos = NULL;
if (arg[0] == '\0') {
quote = 1;
} else {
@@ -1585,26 +1669,22 @@ BuildCommandLine(
/* nothing to escape */
Tcl_DStringAppend(&ds, arg, -1);
} else {
- /* start of argument (open quote-char) */
+ /* start of argument (main opening quote-char) */
Tcl_DStringAppend(&ds, "\"", 1);
start = arg;
for (special = arg; *special != '\0'; ) {
- /* `\\` or `\"` or `\` at end (so equal `\"` because quoted) */
- if (*special == '\\' && (special[1] == '\\' || special[1] == '"' || special[1] == '\0')) {
- if (special > start) {
- Tcl_DStringAppend(&ds, start, (int) (special - start));
- }
- /* escape using backslash */
- Tcl_DStringAppend(&ds, "\\\\", 2);
- start = ++special;
- continue;
+ /* position of `\` is important before quote or at end (equal `\"` because quoted) */
+ if (*special == '\\') {
+ /* bypass backslashes (and mark first backslash possition)*/
+ special = BuildCmdLineBypassBS(special, &bspos);
+ if (*special == '\0') break;
}
/* ["] */
if (*special == '"') {
quote ^= 2; /* invert unpaired flag - observe unpaired quotes */
- if (special > start) {
- Tcl_DStringAppend(&ds, start, (int) (special - start));
- }
+ /* add part before (and escape backslashes before quote) */
+ QuoteCmdLineBackslash(&ds, start, special, bspos);
+ bspos = NULL;
/* escape using backslash */
Tcl_DStringAppend(&ds, "\\\"", 2);
start = ++special;
@@ -1612,27 +1692,25 @@ BuildCommandLine(
}
/* unpaired (escaped) quote causes special handling on meta-chars */
if ((quote & 2) && strchr(specMetaChars, *special)) {
- if (special > start) {
- Tcl_DStringAppend(&ds, start, (int) (special - start));
- }
- /* unpaired - escape all special chars inside quotes like `"..."` */
- Tcl_DStringAppend(&ds, "\"", 1);
- start = special;
- do {
- special++;
- } while(*special && strchr(specMetaChars, *special));
- Tcl_DStringAppend(&ds, start, (int) (special - start));
- Tcl_DStringAppend(&ds, "\"", 1);
- start = special;
+ special = QuoteCmdLinePart(&ds, start, special, specMetaChars, &bspos);
+ /* start to current or first backslash */
+ start = !bspos ? special : bspos;
continue;
}
+ /* special case for % - should be enclosed always (paired also) */
+ if (strchr(specMetaChars2, *special)) {
+ special = QuoteCmdLinePart(&ds, start, special, specMetaChars2, &bspos);
+ /* start to current or first backslash */
+ start = !bspos ? special : bspos;
+ continue;
+ }
+ /* other not special (and not meta) character */
+ bspos = NULL; /* reset last backslash possition (not interesting) */
special++;
}
- /* rest of argument (don't contain special chars) */
- if (special > start) {
- Tcl_DStringAppend(&ds, start, (int) (special - start));
- }
- /* end of argument (closed quote-char) */
+ /* rest of argument (and escape backslashes before closing main quote) */
+ QuoteCmdLineBackslash(&ds, start, special, bspos);
+ /* end of argument (main closing quote-char) */
Tcl_DStringAppend(&ds, "\"", 1);
}
}