[Slim] Encode path to support non-latin characters (#1687)

* [Slim] Add encodePath method

* [Slim] Add tests for encodePath method

* [Slim] Use unescaped path in router

Both variables basePathWithoutHost and path are already urlEncoded in
codegen itself. Builtin html encoding in mustache is redundant. We can
use these raw codegen values with no fear.

* [Slim] Refresh samples
This commit is contained in:
Yuriy Belenko 2018-12-31 07:02:45 +05:00 committed by William Cheng
parent a0e5b74b2b
commit e8ac630ca5
7 changed files with 119 additions and 4 deletions

View File

@ -81,6 +81,8 @@ public interface CodegenConfig {
String escapeTextWhileAllowingNewLines(String text); String escapeTextWhileAllowingNewLines(String text);
String encodePath(String text);
String escapeUnsafeCharacters(String input); String escapeUnsafeCharacters(String input);
String escapeReservedWord(String name); String escapeReservedWord(String name);

View File

@ -557,6 +557,12 @@ public class DefaultCodegen implements CodegenConfig {
.replace("\"", "\\\"")); .replace("\"", "\\\""));
} }
// override with any special encoding and escaping logic
@SuppressWarnings("static-method")
public String encodePath(String input) {
return escapeText(input);
}
/** /**
* override with any special text escaping logic to handle unsafe * override with any special text escaping logic to handle unsafe
* characters so as to avoid code injection * characters so as to avoid code injection

View File

@ -537,9 +537,9 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
} }
}); });
Map<String, Object> operation = processOperations(config, tag, ops, allModels); Map<String, Object> operation = processOperations(config, tag, ops, allModels);
URL url = URLPathUtils.getServerURL(openAPI);
operation.put("basePath", basePath); operation.put("basePath", basePath);
operation.put("basePathWithoutHost", basePathWithoutHost); operation.put("basePathWithoutHost", config.encodePath(url.getPath()).replaceAll("/$", ""));
operation.put("contextPath", contextPath); operation.put("contextPath", contextPath);
operation.put("baseName", tag); operation.put("baseName", tag);
operation.put("apiPackage", config.apiPackage()); operation.put("apiPackage", config.apiPackage());

View File

@ -31,6 +31,13 @@ import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.net.URLEncoder;
import org.apache.commons.lang3.StringEscapeUtils;
import java.io.UnsupportedEncodingException;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.Schema;
public class PhpSlimServerCodegen extends AbstractPhpCodegen { public class PhpSlimServerCodegen extends AbstractPhpCodegen {
private static final Logger LOGGER = LoggerFactory.getLogger(PhpSlimServerCodegen.class); private static final Logger LOGGER = LoggerFactory.getLogger(PhpSlimServerCodegen.class);
@ -179,4 +186,57 @@ public class PhpSlimServerCodegen extends AbstractPhpCodegen {
operations.put(USER_CLASSNAME_KEY, classname); operations.put(USER_CLASSNAME_KEY, classname);
} }
@Override
public String encodePath(String input) {
if (input == null) {
return input;
}
// from DefaultCodegen.java
// remove \t, \n, \r
// replace \ with \\
// replace " with \"
// outter unescape to retain the original multi-byte characters
// finally escalate characters avoiding code injection
input = super.escapeUnsafeCharacters(
StringEscapeUtils.unescapeJava(
StringEscapeUtils.escapeJava(input)
.replace("\\/", "/"))
.replaceAll("[\\t\\n\\r]", " ")
.replace("\\", "\\\\"));
// .replace("\"", "\\\""));
// from AbstractPhpCodegen.java
// Trim the string to avoid leading and trailing spaces.
input = input.trim();
try {
input = URLEncoder.encode(input, "UTF-8")
.replaceAll("\\+", "%20")
.replaceAll("\\%2F", "/")
.replaceAll("\\%7B", "{") // keep { part of complex placeholders
.replaceAll("\\%7D", "}") // } part
.replaceAll("\\%5B", "[") // [ part
.replaceAll("\\%5D", "]") // ] part
.replaceAll("\\%3A", ":") // : part
.replaceAll("\\%2B", "+") // + part
.replaceAll("\\%5C\\%5Cd", "\\\\d"); // \d part
} catch (UnsupportedEncodingException e) {
// continue
LOGGER.error(e.getMessage(), e);
}
return input;
}
@Override
public CodegenOperation fromOperation(String path,
String httpMethod,
Operation operation,
Map<String, Schema> schemas,
OpenAPI openAPI) {
CodegenOperation op = super.fromOperation(path, httpMethod, operation, schemas, openAPI);
op.path = encodePath(path);
return op;
}
} }

View File

@ -64,7 +64,7 @@ class SlimRouter
[ [
'httpMethod' => '{{httpMethod}}', 'httpMethod' => '{{httpMethod}}',
'basePathWithoutHost' => '{{{basePathWithoutHost}}}', 'basePathWithoutHost' => '{{{basePathWithoutHost}}}',
'path' => '{{path}}', 'path' => '{{{path}}}',
'apiPackage' => '{{apiPackage}}', 'apiPackage' => '{{apiPackage}}',
'classname' => '{{classname}}', 'classname' => '{{classname}}',
'userClassname' => '{{userClassname}}', 'userClassname' => '{{userClassname}}',

View File

@ -0,0 +1,47 @@
/*
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openapitools.codegen.slim;
import org.testng.Assert;
import org.testng.annotations.Test;
import org.openapitools.codegen.languages.PhpSlimServerCodegen;
public class PhpSlimServerCodegenTest {
@Test
public void testEncodePath() {
final PhpSlimServerCodegen codegen = new PhpSlimServerCodegen();
Assert.assertEquals(codegen.encodePath("/ ' \" =end -- \\r\\n \\n \\r/v2 *_/ ' \" =end -- \\r\\n \\n \\r/fake"), "/%20%27%20%22%20%3Dend%20--%20%5C%5Cr%5C%5Cn%20%5C%5Cn%20%5C%5Cr/v2%20*_/%20%27%20%22%20%3Dend%20--%20%5C%5Cr%5C%5Cn%20%5C%5Cn%20%5C%5Cr/fake");
Assert.assertEquals(codegen.encodePath("/o\'\"briens/v2/o\'\"henry/fake"), "/o%27%22briens/v2/o%27%22henry/fake");
Assert.assertEquals(codegen.encodePath("/comedians/Chris D\'Elia"), "/comedians/Chris%20D%27Elia");
Assert.assertEquals(codegen.encodePath("/разработчики/Юрий Беленко"), "/%D1%80%D0%B0%D0%B7%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D1%87%D0%B8%D0%BA%D0%B8/%D0%AE%D1%80%D0%B8%D0%B9%20%D0%91%D0%B5%D0%BB%D0%B5%D0%BD%D0%BA%D0%BE");
Assert.assertEquals(codegen.encodePath("/text with multilines \\\n\\\t\\\r"), "/text%20with%20multilines%20%5C%5C%20%5C%5C%20%5C%5C");
Assert.assertEquals(codegen.encodePath("/path with argument {value}"), "/path%20with%20argument%20{value}");
// few examples from Slim documentation
Assert.assertEquals(codegen.encodePath("/users[/{id}]"), "/users[/{id}]");
Assert.assertEquals(codegen.encodePath("/news[/{year}[/{month}]]"), "/news[/{year}[/{month}]]");
Assert.assertEquals(codegen.encodePath("/news[/{params:.*}]"), "/news[/{params:.*}]");
Assert.assertEquals(codegen.encodePath("/users/{id:[0-9]+}"), "/users/{id:[0-9]+}");
// from FastRoute\RouteParser\Std.php
Assert.assertEquals(codegen.encodePath("/user/{name}[/{id:[0-9]+}]"), "/user/{name}[/{id:[0-9]+}]");
Assert.assertEquals(codegen.encodePath("/fixedRoutePart/{varName}[/moreFixed/{varName2:\\d+}]"), "/fixedRoutePart/{varName}[/moreFixed/{varName2:\\d+}]");
}
}

View File

@ -53,7 +53,7 @@ class SlimRouter
private $operations = [ private $operations = [
[ [
'httpMethod' => 'PUT', 'httpMethod' => 'PUT',
'basePathWithoutHost' => '/ ' \" =end -- \\r\\n \\n \\r/v2 *_/ ' \" =end -- \\r\\n \\n \\r', 'basePathWithoutHost' => '/%20%27%20%22%20%3Dend%20--%20%5C%5Cr%5C%5Cn%20%5C%5Cn%20%5C%5Cr/v2%20*_/%20%27%20%22%20%3Dend%20--%20%5C%5Cr%5C%5Cn%20%5C%5Cn%20%5C%5Cr',
'path' => '/fake', 'path' => '/fake',
'apiPackage' => 'OpenAPIServer\Api', 'apiPackage' => 'OpenAPIServer\Api',
'classname' => 'AbstractFakeApi', 'classname' => 'AbstractFakeApi',