vlsi commented on code in PR #6773:
URL: https://github.com/apache/jmeter/pull/6773#discussion_r4098245426
##########
src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java:
##########
@@ -829,50 +829,79 @@ public static String[] translateCommandline(String
toProcess) {
//no command? no string
return new String[0];
}
- // parse with a simple finite state machine
+ // parse with a character-level finite state machine so that
+ // backslash-escaped quotes inside a quoted token are handled correctly
+ // (e.g. 'tes\'t' or "tes\"t").
final int normal = 0;
final int inQuote = 1;
final int inDoubleQuote = 2;
int state = normal;
- final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ",
true);
final ArrayList<String> result = new ArrayList<>();
final StringBuilder current = new StringBuilder();
boolean lastTokenHasBeenQuoted = false;
- while (tok.hasMoreTokens()) {
- String nextTok = tok.nextToken();
+ int i = 0;
+ final int len = toProcess.length();
+ while (i < len) {
+ char c = toProcess.charAt(i);
switch (state) {
case inQuote -> {
- if ("'".equals(nextTok)) {
+ if (c == '\\' && i + 1 < len && toProcess.charAt(i + 1) ==
'\'') {
+ // escaped single-quote inside single-quoted string
+ current.append('\'');
+ i += 2;
+ } else if (c == '\'') {
lastTokenHasBeenQuoted = true;
state = normal;
+ i++;
} else {
- current.append(nextTok);
+ current.append(c);
+ i++;
}
}
case inDoubleQuote -> {
- if ("\"".equals(nextTok)) {
+ if (c == '\\' && i + 1 < len && toProcess.charAt(i + 1) ==
'"') {
Review Comment:
Inside double quotes, the shell treats a backslash as an escape before `"`,
`\`, `$`, `` ` `` and a newline, and keeps it as a literal character before
anything else. Handling only `\"` makes a string that ends in an escaped
backslash impossible to close: `-d "C:\\dir\\" -H "X: y"` parsed on `master`
and now throws `unbalanced quotes`. `"a\\b"` still yields `a\\b` instead of
`a\b`. Please implement the full POSIX set for double quotes.
##########
src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java:
##########
@@ -829,50 +829,79 @@ public static String[] translateCommandline(String
toProcess) {
//no command? no string
return new String[0];
}
- // parse with a simple finite state machine
+ // parse with a character-level finite state machine so that
+ // backslash-escaped quotes inside a quoted token are handled correctly
+ // (e.g. 'tes\'t' or "tes\"t").
final int normal = 0;
final int inQuote = 1;
final int inDoubleQuote = 2;
int state = normal;
- final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ",
true);
final ArrayList<String> result = new ArrayList<>();
final StringBuilder current = new StringBuilder();
boolean lastTokenHasBeenQuoted = false;
- while (tok.hasMoreTokens()) {
- String nextTok = tok.nextToken();
+ int i = 0;
+ final int len = toProcess.length();
+ while (i < len) {
+ char c = toProcess.charAt(i);
switch (state) {
case inQuote -> {
- if ("'".equals(nextTok)) {
+ if (c == '\\' && i + 1 < len && toProcess.charAt(i + 1) ==
'\'') {
Review Comment:
In a POSIX shell, a backslash inside single quotes is a literal character:
nothing can escape the closing `'`. This branch therefore breaks valid
commands. `curl -d 'C:\dir\' -H 'X: y' http://x` passes `C:\dir\` in bash and
on `master`, and now throws `unbalanced quotes`, because the `\'` swallows the
closing quote. The same happens to any regex or JSON value that ends in a
backslash.
`'tes\'t'` from #6374 is not a valid shell word either: bash reports
`unexpected EOF while looking for matching '`. Shells and browsers write a
single quote inside single quotes as `'tes'\''t'` or as `$'tes\'t'`, and this
PR supports neither form. Please drop this branch and follow the POSIX rule;
`'tes'\''t'` will then parse through the existing states.
##########
src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java:
##########
@@ -829,50 +829,79 @@ public static String[] translateCommandline(String
toProcess) {
//no command? no string
return new String[0];
}
- // parse with a simple finite state machine
+ // parse with a character-level finite state machine so that
Review Comment:
This comment describes the change (what is now handled "correctly") and uses
`'tes\'t'`, which is not valid shell syntax, as its example. Please describe
the supported quoting rules in the method's Javadoc and remove this comment.
The comments `// escaped single-quote inside single-quoted string`, `// escaped
double-quote inside double-quoted string` and `// also skip a following \n if
we consumed \r` only repeat the next line of code and can be removed as well.
##########
src/protocol/http/src/test/java/org/apache/jmeter/curl/BasicCurlParserTest.java:
##########
@@ -770,4 +770,126 @@ public void testIsValidCookie() {
assertTrue(BasicCurlParser.isValidCookie("a=b;c=d"), "The string
should be cookies");
assertFalse(BasicCurlParser.isValidCookie("test.txt"), "A filename is
not a valid cookie");
}
+
+ /**
+ * Escaped single-quote inside a single-quoted --data value must not cause
+ * "unbalanced quotes" and must be included literally in the post data.
+ * Reproduces https://github.com/apache/jmeter/issues/6374
+ */
+ @Test
+ public void testEscapedSingleQuoteInData() {
Review Comment:
This test makes `'tes\'t'` (an unterminated quote in bash) a supported
input, so it pins behavior that a POSIX-compliant parser has to reject. The
same applies to `testTranslateCommandlineEscapedSingleQuoteInsideSingleQuotes`
and `testTranslateCommandlineMultipleEscapedQuotes`. Please replace them with
`'tes'\''t'` and add the regression cases listed in the review summary.
##########
src/protocol/http/src/test/java/org/apache/jmeter/curl/BasicCurlParserTest.java:
##########
@@ -770,4 +770,126 @@ public void testIsValidCookie() {
assertTrue(BasicCurlParser.isValidCookie("a=b;c=d"), "The string
should be cookies");
assertFalse(BasicCurlParser.isValidCookie("test.txt"), "A filename is
not a valid cookie");
}
+
+ /**
+ * Escaped single-quote inside a single-quoted --data value must not cause
+ * "unbalanced quotes" and must be included literally in the post data.
+ * Reproduces https://github.com/apache/jmeter/issues/6374
+ */
+ @Test
+ public void testEscapedSingleQuoteInData() {
+ // Shell representation: --data 'tes\'t'
+ // In Java string: the outer single-quotes are literal chars, the \'
is a backslash + single-quote
+ String curl = " curl -X POST \"localhost.com\" --data 'tes\\'t'";
+ BasicCurlParser basicCurlParser = new BasicCurlParser();
+ BasicCurlParser.Request request = basicCurlParser.parse(curl);
+ assertEquals("tes't", request.getPostData(),
+ "Escaped single-quote inside single-quoted data should be
preserved");
+ }
+
+ /**
+ * Escaped double-quote inside a double-quoted --data value must not cause
+ * "unbalanced quotes" and must be included literally in the post data.
+ * Reproduces https://github.com/apache/jmeter/issues/6374
+ */
+ @Test
+ public void testEscapedDoubleQuoteInData() {
+ // Shell representation: --data "tes\"t"
+ String curl = " curl -X POST \"localhost.com\" --data \"tes\\\"t\"";
+ BasicCurlParser basicCurlParser = new BasicCurlParser();
+ BasicCurlParser.Request request = basicCurlParser.parse(curl);
+ assertEquals("tes\"t", request.getPostData(),
+ "Escaped double-quote inside double-quoted data should be
preserved");
+ }
+
+ // -----------------------------------------------------------------------
+ // Direct unit tests for translateCommandline (tokenizer-level coverage)
+ // -----------------------------------------------------------------------
+
+ /** Plain unquoted tokens are split on spaces. */
+ @Test
+ public void testTranslateCommandlineSimpleTokens() {
+ String[] result = BasicCurlParser.translateCommandline("curl -X POST
http://example.com");
+ assertEquals(4, result.length);
+ assertEquals("curl", result[0]);
+ assertEquals("-X", result[1]);
+ assertEquals("POST", result[2]);
+ assertEquals("http://example.com", result[3]);
+ }
+
+ /** Single-quoted token: quotes are stripped, content preserved verbatim.
*/
+ @Test
+ public void testTranslateCommandlineSingleQuotedToken() {
+ String[] result = BasicCurlParser.translateCommandline("curl 'hello
world'");
+ assertEquals(2, result.length);
+ assertEquals("curl", result[0]);
+ assertEquals("hello world", result[1]);
+ }
+
+ /** Double-quoted token: quotes are stripped, content preserved verbatim.
*/
+ @Test
+ public void testTranslateCommandlineDoubleQuotedToken() {
+ String[] result = BasicCurlParser.translateCommandline("curl \"hello
world\"");
+ assertEquals(2, result.length);
+ assertEquals("curl", result[0]);
+ assertEquals("hello world", result[1]);
+ }
+
+ /**
+ * Backslash-escaped single-quote inside a single-quoted token must be
+ * treated as a literal single-quote (fix for issue #6374).
+ */
+ @Test
+ public void testTranslateCommandlineEscapedSingleQuoteInsideSingleQuotes()
{
+ // Input string (as seen by the JVM): 'tes\'t'
+ // i.e. single-quote, t, e, s, backslash, single-quote, t, single-quote
+ String[] result = BasicCurlParser.translateCommandline("'tes\\'t'");
+ assertEquals(1, result.length);
+ assertEquals("tes't", result[0]);
+ }
+
+ /**
+ * Backslash-escaped double-quote inside a double-quoted token must be
+ * treated as a literal double-quote (fix for issue #6374).
+ */
+ @Test
+ public void testTranslateCommandlineEscapedDoubleQuoteInsideDoubleQuotes()
{
+ // Input string (as seen by the JVM): "tes\"t"
+ String[] result = BasicCurlParser.translateCommandline("\"tes\\\"t\"");
+ assertEquals(1, result.length);
+ assertEquals("tes\"t", result[0]);
+ }
+
+ /** Multiple escaped quotes in a single token are all preserved. */
+ @Test
+ public void testTranslateCommandlineMultipleEscapedQuotes() {
+ // 'it\'s a test\'s value' → it's a test's value
+ String[] result = BasicCurlParser.translateCommandline("'it\\'s a
test\\'s value'");
+ assertEquals(1, result.length);
+ assertEquals("it's a test's value", result[0]);
+ }
+
+ /** Backslash + newline (line continuation) outside quotes is consumed
silently. */
+ @Test
+ public void testTranslateCommandlineBackslashLineContinuation() {
+ String[] result = BasicCurlParser.translateCommandline("curl \\\n-d
'hey'");
+ assertEquals(3, result.length);
+ assertEquals("curl", result[0]);
+ assertEquals("-d", result[1]);
+ assertEquals("hey", result[2]);
+ }
+
+ /** Empty input returns an empty array. */
+ @Test
+ public void testTranslateCommandlineEmptyInput() {
+ assertEquals(0, BasicCurlParser.translateCommandline("").length);
+ }
+
+ /** Genuinely unbalanced quotes still throw IllegalArgumentException. */
Review Comment:
`Genuinely` adds nothing here or in the assertion message on line 893:
`Unbalanced quotes throw IllegalArgumentException.` The message should give the
input (`translateCommandline("curl \"unclosed")`) rather than repeat the test
name.
##########
src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java:
##########
@@ -829,50 +829,79 @@ public static String[] translateCommandline(String
toProcess) {
//no command? no string
return new String[0];
}
- // parse with a simple finite state machine
+ // parse with a character-level finite state machine so that
+ // backslash-escaped quotes inside a quoted token are handled correctly
+ // (e.g. 'tes\'t' or "tes\"t").
final int normal = 0;
final int inQuote = 1;
final int inDoubleQuote = 2;
int state = normal;
- final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ",
true);
final ArrayList<String> result = new ArrayList<>();
final StringBuilder current = new StringBuilder();
boolean lastTokenHasBeenQuoted = false;
- while (tok.hasMoreTokens()) {
- String nextTok = tok.nextToken();
+ int i = 0;
+ final int len = toProcess.length();
+ while (i < len) {
+ char c = toProcess.charAt(i);
switch (state) {
case inQuote -> {
- if ("'".equals(nextTok)) {
+ if (c == '\\' && i + 1 < len && toProcess.charAt(i + 1) ==
'\'') {
+ // escaped single-quote inside single-quoted string
+ current.append('\'');
+ i += 2;
+ } else if (c == '\'') {
lastTokenHasBeenQuoted = true;
state = normal;
+ i++;
} else {
- current.append(nextTok);
+ current.append(c);
+ i++;
}
}
case inDoubleQuote -> {
- if ("\"".equals(nextTok)) {
+ if (c == '\\' && i + 1 < len && toProcess.charAt(i + 1) ==
'"') {
+ // escaped double-quote inside double-quoted string
+ current.append('"');
+ i += 2;
+ } else if (c == '"') {
lastTokenHasBeenQuoted = true;
state = normal;
+ i++;
} else {
- current.append(nextTok);
+ current.append(c);
+ i++;
}
}
default -> {
- if ("'".equals(nextTok)) {
+ if (c == '\'') {
Review Comment:
`$'…'` (ANSI-C quoting) is what Chrome's "Copy as cURL (bash)" emits
whenever the body contains a single quote or a non-printable character, e.g.
`--data-raw $'{"name":"O\'Brien"}'`. Before this PR such input failed with
`unbalanced quotes`. Now it parses without an error, and the `$` becomes part
of the request body (`$'tes\'t'` gives `$tes't`). Please either support `$'…'`
or keep rejecting it; sending a corrupted body is worse than an error message.
##########
src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java:
##########
@@ -829,50 +829,79 @@ public static String[] translateCommandline(String
toProcess) {
//no command? no string
return new String[0];
}
- // parse with a simple finite state machine
+ // parse with a character-level finite state machine so that
+ // backslash-escaped quotes inside a quoted token are handled correctly
+ // (e.g. 'tes\'t' or "tes\"t").
final int normal = 0;
final int inQuote = 1;
final int inDoubleQuote = 2;
int state = normal;
- final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ",
true);
final ArrayList<String> result = new ArrayList<>();
final StringBuilder current = new StringBuilder();
boolean lastTokenHasBeenQuoted = false;
- while (tok.hasMoreTokens()) {
- String nextTok = tok.nextToken();
+ int i = 0;
+ final int len = toProcess.length();
+ while (i < len) {
+ char c = toProcess.charAt(i);
switch (state) {
case inQuote -> {
- if ("'".equals(nextTok)) {
+ if (c == '\\' && i + 1 < len && toProcess.charAt(i + 1) ==
'\'') {
+ // escaped single-quote inside single-quoted string
+ current.append('\'');
+ i += 2;
+ } else if (c == '\'') {
lastTokenHasBeenQuoted = true;
state = normal;
+ i++;
} else {
- current.append(nextTok);
+ current.append(c);
+ i++;
}
}
case inDoubleQuote -> {
- if ("\"".equals(nextTok)) {
+ if (c == '\\' && i + 1 < len && toProcess.charAt(i + 1) ==
'"') {
+ // escaped double-quote inside double-quoted string
+ current.append('"');
+ i += 2;
+ } else if (c == '"') {
lastTokenHasBeenQuoted = true;
state = normal;
+ i++;
} else {
- current.append(nextTok);
+ current.append(c);
+ i++;
}
}
default -> {
- if ("'".equals(nextTok)) {
+ if (c == '\'') {
state = inQuote;
- } else if ("\"".equals(nextTok)) {
+ i++;
+ } else if (c == '"') {
state = inDoubleQuote;
- } else if (" ".equals(nextTok)) {
+ i++;
+ } else if (c == ' ') {
if (lastTokenHasBeenQuoted || !current.isEmpty()) {
result.add(current.toString());
current.setLength(0);
}
+ lastTokenHasBeenQuoted = false;
+ i++;
+ } else if (c == '\\' && i + 1 < len
Review Comment:
Outside quotes, a POSIX shell treats a backslash as an escape for any
character, not only for a line break: `tes\'t` is `tes't`. With this change
`tes\'t` still opens a quote and throws.
This branch also changes how `\` followed by CRLF is handled: `master`
produced an extra argument `"\n"`, and this code drops it. Please add a test
for CRLF and mention the change in the PR description.
##########
src/protocol/http/src/test/java/org/apache/jmeter/curl/BasicCurlParserTest.java:
##########
@@ -770,4 +770,126 @@ public void testIsValidCookie() {
assertTrue(BasicCurlParser.isValidCookie("a=b;c=d"), "The string
should be cookies");
assertFalse(BasicCurlParser.isValidCookie("test.txt"), "A filename is
not a valid cookie");
}
+
+ /**
+ * Escaped single-quote inside a single-quoted --data value must not cause
+ * "unbalanced quotes" and must be included literally in the post data.
+ * Reproduces https://github.com/apache/jmeter/issues/6374
+ */
+ @Test
+ public void testEscapedSingleQuoteInData() {
+ // Shell representation: --data 'tes\'t'
+ // In Java string: the outer single-quotes are literal chars, the \'
is a backslash + single-quote
+ String curl = " curl -X POST \"localhost.com\" --data 'tes\\'t'";
+ BasicCurlParser basicCurlParser = new BasicCurlParser();
+ BasicCurlParser.Request request = basicCurlParser.parse(curl);
+ assertEquals("tes't", request.getPostData(),
+ "Escaped single-quote inside single-quoted data should be
preserved");
+ }
+
+ /**
+ * Escaped double-quote inside a double-quoted --data value must not cause
+ * "unbalanced quotes" and must be included literally in the post data.
+ * Reproduces https://github.com/apache/jmeter/issues/6374
+ */
+ @Test
+ public void testEscapedDoubleQuoteInData() {
+ // Shell representation: --data "tes\"t"
+ String curl = " curl -X POST \"localhost.com\" --data \"tes\\\"t\"";
+ BasicCurlParser basicCurlParser = new BasicCurlParser();
+ BasicCurlParser.Request request = basicCurlParser.parse(curl);
+ assertEquals("tes\"t", request.getPostData(),
+ "Escaped double-quote inside double-quoted data should be
preserved");
+ }
+
+ // -----------------------------------------------------------------------
+ // Direct unit tests for translateCommandline (tokenizer-level coverage)
Review Comment:
Please remove the banner comment. The tokenizer cases differ only in the
input and the expected tokens, so they fit one `@ParameterizedTest` with a name
per case (for example `@MethodSource` with `Arguments.of("backslash before
closing single quote", "-d 'C:\\dir\\'", new String[]{"-d", "C:\\dir\\"})`).
The expected tokens for each case should come from bash.
##########
.github/workflows/gradle-wrapper-validation.yml:
##########
@@ -7,4 +7,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- - uses:
gradle/actions/wrapper-validation@0723195856401067f7a2779048b490ace7a47d7c #
v5.0.2
+ - uses:
gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 #
v6.2.0
Review Comment:
This change is unrelated to #6374; please move it to its own PR. In the ASF
allowlist ([`apache/infrastructure-actions`
`actions.yml`](https://github.com/apache/infrastructure-actions/blob/main/actions.yml)),
`wrapper-validation@3f131e8…` (v6.2.0) has `expires_at: 2026-10-30`, so the
workflow will fail again in five weeks. v6.3.0
(`9c971963bec38e04b3d30dcc455b5382be2fdbfb`) has no expiry date.
##########
src/protocol/http/src/test/java/org/apache/jmeter/curl/BasicCurlParserTest.java:
##########
@@ -770,4 +770,126 @@ public void testIsValidCookie() {
assertTrue(BasicCurlParser.isValidCookie("a=b;c=d"), "The string
should be cookies");
assertFalse(BasicCurlParser.isValidCookie("test.txt"), "A filename is
not a valid cookie");
}
+
+ /**
+ * Escaped single-quote inside a single-quoted --data value must not cause
+ * "unbalanced quotes" and must be included literally in the post data.
+ * Reproduces https://github.com/apache/jmeter/issues/6374
+ */
+ @Test
+ public void testEscapedSingleQuoteInData() {
+ // Shell representation: --data 'tes\'t'
+ // In Java string: the outer single-quotes are literal chars, the \'
is a backslash + single-quote
+ String curl = " curl -X POST \"localhost.com\" --data 'tes\\'t'";
+ BasicCurlParser basicCurlParser = new BasicCurlParser();
+ BasicCurlParser.Request request = basicCurlParser.parse(curl);
+ assertEquals("tes't", request.getPostData(),
+ "Escaped single-quote inside single-quoted data should be
preserved");
+ }
+
+ /**
+ * Escaped double-quote inside a double-quoted --data value must not cause
+ * "unbalanced quotes" and must be included literally in the post data.
+ * Reproduces https://github.com/apache/jmeter/issues/6374
+ */
+ @Test
+ public void testEscapedDoubleQuoteInData() {
+ // Shell representation: --data "tes\"t"
+ String curl = " curl -X POST \"localhost.com\" --data \"tes\\\"t\"";
+ BasicCurlParser basicCurlParser = new BasicCurlParser();
+ BasicCurlParser.Request request = basicCurlParser.parse(curl);
+ assertEquals("tes\"t", request.getPostData(),
+ "Escaped double-quote inside double-quoted data should be
preserved");
+ }
+
+ // -----------------------------------------------------------------------
+ // Direct unit tests for translateCommandline (tokenizer-level coverage)
+ // -----------------------------------------------------------------------
+
+ /** Plain unquoted tokens are split on spaces. */
+ @Test
+ public void testTranslateCommandlineSimpleTokens() {
+ String[] result = BasicCurlParser.translateCommandline("curl -X POST
http://example.com");
+ assertEquals(4, result.length);
+ assertEquals("curl", result[0]);
+ assertEquals("-X", result[1]);
+ assertEquals("POST", result[2]);
+ assertEquals("http://example.com", result[3]);
+ }
+
+ /** Single-quoted token: quotes are stripped, content preserved verbatim.
*/
+ @Test
+ public void testTranslateCommandlineSingleQuotedToken() {
+ String[] result = BasicCurlParser.translateCommandline("curl 'hello
world'");
+ assertEquals(2, result.length);
+ assertEquals("curl", result[0]);
+ assertEquals("hello world", result[1]);
+ }
+
+ /** Double-quoted token: quotes are stripped, content preserved verbatim.
*/
+ @Test
+ public void testTranslateCommandlineDoubleQuotedToken() {
+ String[] result = BasicCurlParser.translateCommandline("curl \"hello
world\"");
+ assertEquals(2, result.length);
+ assertEquals("curl", result[0]);
+ assertEquals("hello world", result[1]);
+ }
+
+ /**
+ * Backslash-escaped single-quote inside a single-quoted token must be
+ * treated as a literal single-quote (fix for issue #6374).
+ */
+ @Test
+ public void testTranslateCommandlineEscapedSingleQuoteInsideSingleQuotes()
{
+ // Input string (as seen by the JVM): 'tes\'t'
+ // i.e. single-quote, t, e, s, backslash, single-quote, t, single-quote
+ String[] result = BasicCurlParser.translateCommandline("'tes\\'t'");
+ assertEquals(1, result.length);
+ assertEquals("tes't", result[0]);
+ }
+
+ /**
+ * Backslash-escaped double-quote inside a double-quoted token must be
+ * treated as a literal double-quote (fix for issue #6374).
+ */
+ @Test
+ public void testTranslateCommandlineEscapedDoubleQuoteInsideDoubleQuotes()
{
+ // Input string (as seen by the JVM): "tes\"t"
+ String[] result = BasicCurlParser.translateCommandline("\"tes\\\"t\"");
+ assertEquals(1, result.length);
+ assertEquals("tes\"t", result[0]);
+ }
+
+ /** Multiple escaped quotes in a single token are all preserved. */
+ @Test
+ public void testTranslateCommandlineMultipleEscapedQuotes() {
+ // 'it\'s a test\'s value' → it's a test's value
+ String[] result = BasicCurlParser.translateCommandline("'it\\'s a
test\\'s value'");
+ assertEquals(1, result.length);
+ assertEquals("it's a test's value", result[0]);
+ }
+
+ /** Backslash + newline (line continuation) outside quotes is consumed
silently. */
+ @Test
+ public void testTranslateCommandlineBackslashLineContinuation() {
Review Comment:
This duplicates the existing `testBackslashAtLineEnding` (`"curl \\\n-d
'hey' …"`). The case this PR changes is `\` followed by `\r\n`, which has no
test.
##########
src/protocol/http/src/test/java/org/apache/jmeter/curl/BasicCurlParserTest.java:
##########
@@ -770,4 +770,126 @@ public void testIsValidCookie() {
assertTrue(BasicCurlParser.isValidCookie("a=b;c=d"), "The string
should be cookies");
assertFalse(BasicCurlParser.isValidCookie("test.txt"), "A filename is
not a valid cookie");
}
+
+ /**
+ * Escaped single-quote inside a single-quoted --data value must not cause
+ * "unbalanced quotes" and must be included literally in the post data.
+ * Reproduces https://github.com/apache/jmeter/issues/6374
+ */
+ @Test
+ public void testEscapedSingleQuoteInData() {
+ // Shell representation: --data 'tes\'t'
+ // In Java string: the outer single-quotes are literal chars, the \'
is a backslash + single-quote
+ String curl = " curl -X POST \"localhost.com\" --data 'tes\\'t'";
+ BasicCurlParser basicCurlParser = new BasicCurlParser();
+ BasicCurlParser.Request request = basicCurlParser.parse(curl);
+ assertEquals("tes't", request.getPostData(),
+ "Escaped single-quote inside single-quoted data should be
preserved");
+ }
+
+ /**
+ * Escaped double-quote inside a double-quoted --data value must not cause
+ * "unbalanced quotes" and must be included literally in the post data.
+ * Reproduces https://github.com/apache/jmeter/issues/6374
+ */
+ @Test
+ public void testEscapedDoubleQuoteInData() {
+ // Shell representation: --data "tes\"t"
+ String curl = " curl -X POST \"localhost.com\" --data \"tes\\\"t\"";
+ BasicCurlParser basicCurlParser = new BasicCurlParser();
+ BasicCurlParser.Request request = basicCurlParser.parse(curl);
+ assertEquals("tes\"t", request.getPostData(),
+ "Escaped double-quote inside double-quoted data should be
preserved");
+ }
+
+ // -----------------------------------------------------------------------
+ // Direct unit tests for translateCommandline (tokenizer-level coverage)
+ // -----------------------------------------------------------------------
+
+ /** Plain unquoted tokens are split on spaces. */
+ @Test
+ public void testTranslateCommandlineSimpleTokens() {
+ String[] result = BasicCurlParser.translateCommandline("curl -X POST
http://example.com");
+ assertEquals(4, result.length);
Review Comment:
`assertEquals(4, result.length)` followed by per-element `assertEquals`
stops at the first mismatch and never prints the actual tokens.
`assertArrayEquals(new String[]{"curl", "-X", "POST", "http://example.com"},
result)` prints both arrays when it fails. The same pattern appears in every
`testTranslateCommandline*` test.
##########
src/protocol/http/src/test/java/org/apache/jmeter/curl/BasicCurlParserTest.java:
##########
@@ -770,4 +770,126 @@ public void testIsValidCookie() {
assertTrue(BasicCurlParser.isValidCookie("a=b;c=d"), "The string
should be cookies");
assertFalse(BasicCurlParser.isValidCookie("test.txt"), "A filename is
not a valid cookie");
}
+
+ /**
+ * Escaped single-quote inside a single-quoted --data value must not cause
+ * "unbalanced quotes" and must be included literally in the post data.
+ * Reproduces https://github.com/apache/jmeter/issues/6374
+ */
+ @Test
+ public void testEscapedSingleQuoteInData() {
+ // Shell representation: --data 'tes\'t'
+ // In Java string: the outer single-quotes are literal chars, the \'
is a backslash + single-quote
+ String curl = " curl -X POST \"localhost.com\" --data 'tes\\'t'";
+ BasicCurlParser basicCurlParser = new BasicCurlParser();
+ BasicCurlParser.Request request = basicCurlParser.parse(curl);
+ assertEquals("tes't", request.getPostData(),
+ "Escaped single-quote inside single-quoted data should be
preserved");
+ }
+
+ /**
+ * Escaped double-quote inside a double-quoted --data value must not cause
+ * "unbalanced quotes" and must be included literally in the post data.
+ * Reproduces https://github.com/apache/jmeter/issues/6374
+ */
+ @Test
+ public void testEscapedDoubleQuoteInData() {
+ // Shell representation: --data "tes\"t"
+ String curl = " curl -X POST \"localhost.com\" --data \"tes\\\"t\"";
+ BasicCurlParser basicCurlParser = new BasicCurlParser();
+ BasicCurlParser.Request request = basicCurlParser.parse(curl);
+ assertEquals("tes\"t", request.getPostData(),
+ "Escaped double-quote inside double-quoted data should be
preserved");
+ }
+
+ // -----------------------------------------------------------------------
+ // Direct unit tests for translateCommandline (tokenizer-level coverage)
+ // -----------------------------------------------------------------------
+
+ /** Plain unquoted tokens are split on spaces. */
+ @Test
+ public void testTranslateCommandlineSimpleTokens() {
+ String[] result = BasicCurlParser.translateCommandline("curl -X POST
http://example.com");
+ assertEquals(4, result.length);
+ assertEquals("curl", result[0]);
+ assertEquals("-X", result[1]);
+ assertEquals("POST", result[2]);
+ assertEquals("http://example.com", result[3]);
+ }
+
+ /** Single-quoted token: quotes are stripped, content preserved verbatim.
*/
+ @Test
+ public void testTranslateCommandlineSingleQuotedToken() {
+ String[] result = BasicCurlParser.translateCommandline("curl 'hello
world'");
+ assertEquals(2, result.length);
+ assertEquals("curl", result[0]);
+ assertEquals("hello world", result[1]);
+ }
+
+ /** Double-quoted token: quotes are stripped, content preserved verbatim.
*/
+ @Test
+ public void testTranslateCommandlineDoubleQuotedToken() {
+ String[] result = BasicCurlParser.translateCommandline("curl \"hello
world\"");
+ assertEquals(2, result.length);
+ assertEquals("curl", result[0]);
+ assertEquals("hello world", result[1]);
+ }
+
+ /**
+ * Backslash-escaped single-quote inside a single-quoted token must be
+ * treated as a literal single-quote (fix for issue #6374).
Review Comment:
`(fix for issue #6374)` gives the issue number instead of the defect. Please
state the rule the test checks and, if needed, the old failure in one sentence
(`used to throw IllegalArgumentException: unbalanced quotes`), with the issue
number after it. The same applies to line 853.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]