Skip to content

[PHP/Dart/Python] Correctly escape strings in single quotes (Fixes #17582) #19529

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,20 @@ public String escapeText(String input) {
.replace("\"", "\\\""));
}

/**
* This method escapes text to be used in a single quoted string
* @param input the input string
* @return the escaped string
*/
public String escapeTextInSingleQuotes(String input) {
if (input == null) {
return null;
}

return escapeText(input).replace("'", "\\'");
}


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in abstract typescript codegen, it overrides escapeText to replace ' with \\'

what about follow the same without introducing escapeTextInSingleQuotes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several issues with escaping single quotes by default that would also affect other generators:

  • Some languages (e.g., PHP) treat '\"' as a string containing a backslash and a double quote
  • ' is often used in text like parameter descriptions and would be escaped in comments, which leads to doc blocks like this:
    image
  • Escaping a single quote is illegal in some formats (e.g., JSON)

Ideally, the context a text is used in (e.g., JSON, single-quoted string, double-quoted string or comment) could be taken into account when a string is escaped, either by making them individual methods or by adding a parameter for that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the explanation. let me think about it and review accordingly

/**
* Escape characters while allowing new lines
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ public String toEnumValue(String value, String datatype) {
"int".equalsIgnoreCase(datatype)) {
return value;
} else {
return "'" + escapeText(value) + "'";
return "'" + escapeTextInSingleQuotes(value) + "'";
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -634,9 +634,10 @@ public void setParameterExampleValue(CodegenParameter p) {

if ("String".equalsIgnoreCase(type) || p.isString) {
if (example == null) {
example = "'" + p.paramName + "_example'";
example = "'" + escapeTextInSingleQuotes(p.paramName) + "_example'";
} else {
example = escapeText(example);
}
example = escapeText(example);
} else if ("Integer".equals(type) || "int".equals(type)) {
if (example == null) {
example = "56";
Expand All @@ -653,17 +654,17 @@ public void setParameterExampleValue(CodegenParameter p) {
if (example == null) {
example = "/path/to/file.txt";
}
example = "\"" + escapeText(example) + "\"";
example = "'" + escapeTextInSingleQuotes(example) + "'";
} else if ("\\Date".equalsIgnoreCase(type)) {
if (example == null) {
example = "2013-10-20";
}
example = "new \\DateTime(\"" + escapeText(example) + "\")";
example = "new \\DateTime('" + escapeTextInSingleQuotes(example) + "')";
} else if ("\\DateTime".equalsIgnoreCase(type)) {
if (example == null) {
example = "2013-10-20T19:20:30+01:00";
}
example = "new \\DateTime(\"" + escapeText(example) + "\")";
example = "new \\DateTime('" + escapeTextInSingleQuotes(example) + "')";
} else if ("object".equals(type)) {
example = "new \\stdClass";
} else if (!languageSpecificPrimitives.contains(type)) {
Expand All @@ -689,7 +690,7 @@ public String toEnumValue(String value, String datatype) {
if ("int".equals(datatype) || "float".equals(datatype)) {
return value;
} else {
return "\'" + escapeText(value) + "\'";
return "'" + escapeTextInSingleQuotes(value) + "'";
}
}

Expand Down Expand Up @@ -800,6 +801,16 @@ public String escapeText(String input) {
return super.escapeText(input).trim();
}

@Override
public String escapeTextInSingleQuotes(String input) {
if (input == null) {
return input;
}

// Unescape double quotes because PHP keeps the backslashes if a character does not need to be escaped
return super.escapeTextInSingleQuotes(input).replace("\\\"", "\"");
}

public void escapeMediaType(List<CodegenOperation> operationList) {
for (CodegenOperation op : operationList) {
if (!op.hasProduces) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ private String toExampleValueRecursive(Schema schema, List<Schema> includedSchem
// Enum case:
example = schema.getEnum().get(0).toString();
if (ModelUtils.isStringSchema(schema)) {
example = "'" + escapeText(example) + "'";
example = "'" + escapeTextInSingleQuotes(example) + "'";
}
if (null == example)
LOGGER.warn("Empty enum. Cannot built an example!");
Expand Down Expand Up @@ -521,7 +521,7 @@ private String toExampleValueRecursive(Schema schema, List<Schema> includedSchem
if (additional.getEnum() != null && !additional.getEnum().isEmpty()) {
theKey = additional.getEnum().get(0).toString();
if (ModelUtils.isStringSchema(additional)) {
theKey = "'" + escapeText(theKey) + "'";
theKey = "'" + escapeTextInSingleQuotes(theKey) + "'";
}
}
example = "{\n" + indentationString + theKey + " : " + toExampleValueRecursive(additional, includedSchemas, indentation + 1) + "\n" + indentationString + "}";
Expand Down Expand Up @@ -587,7 +587,7 @@ private String toExampleValueRecursive(Schema schema, List<Schema> includedSchem
}

if (ModelUtils.isStringSchema(schema)) {
example = "'" + escapeText(example) + "'";
example = "'" + escapeTextInSingleQuotes(example) + "'";
}

return example;
Expand All @@ -613,7 +613,7 @@ public void setParameterExampleValue(CodegenParameter p) {
if (example == null) {
example = p.paramName + "_example";
}
example = "'" + escapeText(example) + "'";
example = "'" + escapeTextInSingleQuotes(example) + "'";
} else if ("Integer".equals(type) || "int".equals(type)) {
if (example == null) {
example = "56";
Expand All @@ -630,17 +630,17 @@ public void setParameterExampleValue(CodegenParameter p) {
if (example == null) {
example = "/path/to/file";
}
example = "'" + escapeText(example) + "'";
example = "'" + escapeTextInSingleQuotes(example) + "'";
} else if ("Date".equalsIgnoreCase(type)) {
if (example == null) {
example = "2013-10-20";
}
example = "'" + escapeText(example) + "'";
example = "'" + escapeTextInSingleQuotes(example) + "'";
} else if ("DateTime".equalsIgnoreCase(type)) {
if (example == null) {
example = "2013-10-20T19:20:30+01:00";
}
example = "'" + escapeText(example) + "'";
example = "'" + escapeTextInSingleQuotes(example) + "'";
} else if (!languageSpecificPrimitives.contains(type)) {
// type is a model class, e.g. User
example = this.packageName + "." + type + "()";
Expand Down Expand Up @@ -1412,7 +1412,7 @@ public String toEnumValue(String value, String datatype) {
if ("int".equals(datatype) || "float".equals(datatype)) {
return value;
} else {
return "\'" + escapeText(value) + "\'";
return "'" + escapeTextInSingleQuotes(value) + "'";
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,6 @@ public String controllerFileFolder() {
return (outputFolder + File.separator + toSrcPath(controllerPackage, srcBasePath));
}

@Override
public String escapeText(String input) {
if (input != null) {
// Trim the string to avoid leading and trailing spaces.
return super.escapeText(input).trim();
}
return input;
}

@Override
public CodegenType getTag() {
return CodegenType.SERVER;
Expand Down Expand Up @@ -577,15 +568,6 @@ public String toModelImport(String name) {
}
}

@Override
public String toEnumValue(String value, String datatype) {
if ("int".equals(datatype) || "float".equals(datatype)) {
return value;
} else {
return "\"" + escapeText(value) + "\"";
}
}

/**
* Return the regular expression/JSON schema pattern (http://json-schema.org/latest/json-schema-validation.html#anchor33)
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,15 @@ public void testKeywords() throws Exception {
}
}


@Test(description = "Enum value with quotes (#17582)")
public void testEnumPropertyWithQuotes() {
final DartClientCodegen codegen = new DartClientCodegen();

Assert.assertEquals(codegen.toEnumValue("enum-value", "string"), "'enum-value'");
Assert.assertEquals(codegen.toEnumValue("won't fix", "string"), "'won\\'t fix'");
Assert.assertEquals(codegen.toEnumValue("\"", "string"), "'\\\"'");
Assert.assertEquals(codegen.toEnumValue("1.0", "number"), "1.0");
Assert.assertEquals(codegen.toEnumValue("1", "int"), "1");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,13 @@ public void testEnumPropertyWithDefaultValue() {
CodegenProperty cp1 = cm1.vars.get(0);
Assert.assertEquals(cp1.getDefaultValue(), "'VALUE'");
}

@Test(description = "Enum value with quotes (#17582)")
public void testEnumPropertyWithQuotes() {
Assert.assertEquals(codegen.toEnumValue("enum-value", "string"), "'enum-value'");
Assert.assertEquals(codegen.toEnumValue("won't fix", "string"), "'won\\'t fix'");
Assert.assertEquals(codegen.toEnumValue("\"", "string"), "'\"'");
Assert.assertEquals(codegen.toEnumValue("1.0", "float"), "1.0");
Assert.assertEquals(codegen.toEnumValue("1", "int"), "1");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -530,4 +530,15 @@ public void testHandleConstantParams() throws IOException {
assertFileContains(apiFile.toPath(), "_header_params['X-CUSTOM_CONSTANT_HEADER'] = 'CONSTANT_VALUE'");
assertFileContains(apiFile.toPath(), "_query_params.append(('CONSTANT_QUERY_STRING_KEY', 'CONSTANT_QUERY_STRING_VALUE'))");
}

@Test(description = "Enum value with quotes (#17582)")
public void testEnumPropertyWithQuotes() {
final PythonClientCodegen codegen = new PythonClientCodegen();

Assert.assertEquals(codegen.toEnumValue("enum-value", "string"), "'enum-value'");
Assert.assertEquals(codegen.toEnumValue("won't fix", "string"), "'won\\'t fix'");
Assert.assertEquals(codegen.toEnumValue("\"", "string"), "'\\\"'");
Assert.assertEquals(codegen.toEnumValue("1.0", "float"), "1.0");
Assert.assertEquals(codegen.toEnumValue("1", "int"), "1");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ $apiInstance = new OpenAPI\Client\Api\BodyApi(
// This is optional, `GuzzleHttp\Client` will be used as default.
new GuzzleHttp\Client()
);
$body = "/path/to/file.txt"; // \Psr\Http\Message\StreamInterface
$body = '/path/to/file.txt'; // \Psr\Http\Message\StreamInterface

try {
$result = $apiInstance->testBodyApplicationOctetstreamBinary($body);
Expand Down Expand Up @@ -148,7 +148,7 @@ $apiInstance = new OpenAPI\Client\Api\BodyApi(
// This is optional, `GuzzleHttp\Client` will be used as default.
new GuzzleHttp\Client()
);
$files = array("/path/to/file.txt"); // \Psr\Http\Message\StreamInterface[]
$files = array('/path/to/file.txt'); // \Psr\Http\Message\StreamInterface[]

try {
$result = $apiInstance->testBodyMultipartFormdataArrayOfBinary($files);
Expand Down Expand Up @@ -204,7 +204,7 @@ $apiInstance = new OpenAPI\Client\Api\BodyApi(
// This is optional, `GuzzleHttp\Client` will be used as default.
new GuzzleHttp\Client()
);
$my_file = "/path/to/file.txt"; // \Psr\Http\Message\StreamInterface
$my_file = '/path/to/file.txt'; // \Psr\Http\Message\StreamInterface

try {
$result = $apiInstance->testBodyMultipartFormdataSingleBinary($my_file);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ $apiInstance = new OpenAPI\Client\Api\QueryApi(
// This is optional, `GuzzleHttp\Client` will be used as default.
new GuzzleHttp\Client()
);
$datetime_query = new \DateTime("2013-10-20T19:20:30+01:00"); // \DateTime
$date_query = new \DateTime("2013-10-20T19:20:30+01:00"); // \DateTime
$datetime_query = new \DateTime('2013-10-20T19:20:30+01:00'); // \DateTime
$date_query = new \DateTime('2013-10-20T19:20:30+01:00'); // \DateTime
$string_query = 'string_query_example'; // string

try {
Expand Down
6 changes: 3 additions & 3 deletions samples/client/echo_api/php-nextgen/docs/Api/BodyApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ $apiInstance = new OpenAPI\Client\Api\BodyApi(
// This is optional, `GuzzleHttp\Client` will be used as default.
new GuzzleHttp\Client()
);
$body = "/path/to/file.txt"; // \SplFileObject
$body = '/path/to/file.txt'; // \SplFileObject

try {
$result = $apiInstance->testBodyApplicationOctetstreamBinary($body);
Expand Down Expand Up @@ -148,7 +148,7 @@ $apiInstance = new OpenAPI\Client\Api\BodyApi(
// This is optional, `GuzzleHttp\Client` will be used as default.
new GuzzleHttp\Client()
);
$files = array("/path/to/file.txt"); // \SplFileObject[]
$files = array('/path/to/file.txt'); // \SplFileObject[]

try {
$result = $apiInstance->testBodyMultipartFormdataArrayOfBinary($files);
Expand Down Expand Up @@ -204,7 +204,7 @@ $apiInstance = new OpenAPI\Client\Api\BodyApi(
// This is optional, `GuzzleHttp\Client` will be used as default.
new GuzzleHttp\Client()
);
$my_file = "/path/to/file.txt"; // \SplFileObject
$my_file = '/path/to/file.txt'; // \SplFileObject

try {
$result = $apiInstance->testBodyMultipartFormdataSingleBinary($my_file);
Expand Down
4 changes: 2 additions & 2 deletions samples/client/echo_api/php-nextgen/docs/Api/QueryApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ $apiInstance = new OpenAPI\Client\Api\QueryApi(
// This is optional, `GuzzleHttp\Client` will be used as default.
new GuzzleHttp\Client()
);
$datetime_query = new \DateTime("2013-10-20T19:20:30+01:00"); // \DateTime
$date_query = new \DateTime("2013-10-20T19:20:30+01:00"); // \DateTime
$datetime_query = new \DateTime('2013-10-20T19:20:30+01:00'); // \DateTime
$date_query = new \DateTime('2013-10-20T19:20:30+01:00'); // \DateTime
$string_query = 'string_query_example'; // string

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ $apiInstance = new OpenAPI\Client\Api\FakeApi(
// This is optional, `GuzzleHttp\Client` will be used as default.
new GuzzleHttp\Client()
);
$body = "/path/to/file.txt"; // \SplFileObject | image to upload
$body = '/path/to/file.txt'; // \SplFileObject | image to upload

try {
$apiInstance->testBodyWithBinary($body);
Expand Down Expand Up @@ -785,9 +785,9 @@ $int32 = 56; // int | None
$int64 = 56; // int | None
$float = 3.4; // float | None
$string = 'string_example'; // string | None
$binary = "/path/to/file.txt"; // \SplFileObject | None
$date = new \DateTime("2013-10-20T19:20:30+01:00"); // \DateTime | None
$date_time = new \DateTime("2013-10-20T19:20:30+01:00"); // \DateTime | None
$binary = '/path/to/file.txt'; // \SplFileObject | None
$date = new \DateTime('2013-10-20T19:20:30+01:00'); // \DateTime | None
$date_time = new \DateTime('2013-10-20T19:20:30+01:00'); // \DateTime | None
$password = 'password_example'; // string | None
$callback = 'callback_example'; // string | None

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ $apiInstance = new OpenAPI\Client\Api\PetApi(
);
$pet_id = 56; // int | ID of pet to update
$additional_metadata = 'additional_metadata_example'; // string | Additional data to pass to server
$file = "/path/to/file.txt"; // \SplFileObject | file to upload
$file = '/path/to/file.txt'; // \SplFileObject | file to upload

try {
$result = $apiInstance->uploadFile($pet_id, $additional_metadata, $file);
Expand Down Expand Up @@ -577,7 +577,7 @@ $apiInstance = new OpenAPI\Client\Api\PetApi(
$config
);
$pet_id = 56; // int | ID of pet to update
$required_file = "/path/to/file.txt"; // \SplFileObject | file to upload
$required_file = '/path/to/file.txt'; // \SplFileObject | file to upload
$additional_metadata = 'additional_metadata_example'; // string | Additional data to pass to server

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ $apiInstance = new OpenAPI\Client\Api\FakeApi(
// This is optional, `GuzzleHttp\Client` will be used as default.
new GuzzleHttp\Client()
);
$body = "/path/to/file.txt"; // \SplFileObject | image to upload
$body = '/path/to/file.txt'; // \SplFileObject | image to upload

try {
$apiInstance->testBodyWithBinary($body);
Expand Down Expand Up @@ -846,9 +846,9 @@ $int32 = 56; // int | None
$int64 = 56; // int | None
$float = 3.4; // float | None
$string = 'string_example'; // string | None
$binary = "/path/to/file.txt"; // \SplFileObject | None
$date = new \DateTime("2013-10-20T19:20:30+01:00"); // \DateTime | None
$date_time = new \DateTime("2013-10-20T19:20:30+01:00"); // \DateTime | None
$binary = '/path/to/file.txt'; // \SplFileObject | None
$date = new \DateTime('2013-10-20T19:20:30+01:00'); // \DateTime | None
$date_time = new \DateTime('2013-10-20T19:20:30+01:00'); // \DateTime | None
$password = 'password_example'; // string | None
$callback = 'callback_example'; // string | None

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ $apiInstance = new OpenAPI\Client\Api\PetApi(
);
$pet_id = 56; // int | ID of pet to update
$additional_metadata = 'additional_metadata_example'; // string | Additional data to pass to server
$file = "/path/to/file.txt"; // \SplFileObject | file to upload
$file = '/path/to/file.txt'; // \SplFileObject | file to upload

try {
$result = $apiInstance->uploadFile($pet_id, $additional_metadata, $file);
Expand Down Expand Up @@ -577,7 +577,7 @@ $apiInstance = new OpenAPI\Client\Api\PetApi(
$config
);
$pet_id = 56; // int | ID of pet to update
$required_file = "/path/to/file.txt"; // \SplFileObject | file to upload
$required_file = '/path/to/file.txt'; // \SplFileObject | file to upload
$additional_metadata = 'additional_metadata_example'; // string | Additional data to pass to server

try {
Expand Down
Loading
Loading