Skip to content

Commit aaddd0d

Browse files
authored
Bug fix for handling of common special characters when building paths (#750) (#757)
Co-authored-by: simatosc <[email protected]>
1 parent 5b5a192 commit aaddd0d

File tree

2 files changed

+51
-64
lines changed

2 files changed

+51
-64
lines changed

src/main/java/com/networknt/schema/PathType.java

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public enum PathType {
1111
/**
1212
* The legacy approach, loosely based on JSONPath (but not guaranteed to give valid JSONPath expressions).
1313
*/
14-
LEGACY("$", (token) -> "." + token, (index) -> "[" + index + "]"),
14+
LEGACY("$", (token) -> "." + replaceCommonSpecialCharactersIfPresent(token), (index) -> "[" + index + "]"),
1515

1616
/**
1717
* Paths as JSONPath expressions.
@@ -22,7 +22,6 @@ public enum PathType {
2222
throw new IllegalArgumentException("A JSONPath selector cannot be empty");
2323
}
2424

25-
String t = token;
2625
/*
2726
* Accepted characters for shorthand paths:
2827
* - 'a' through 'z'
@@ -31,17 +30,16 @@ public enum PathType {
3130
* - Underscore ('_')
3231
* - any non-ASCII Unicode character
3332
*/
34-
if (JSONPath.isShorthand(t)) {
35-
return "." + t;
33+
if (JSONPath.isShorthand(token)) {
34+
return "." + token;
3635
}
3736

38-
boolean containsApostrophe = 0 <= t.indexOf('\'');
39-
if (containsApostrophe) {
40-
// Make sure also any apostrophes are escaped.
41-
t = t.replace("'", "\\'");
42-
}
37+
// Replace single quote (used to wrap property names when not shorthand form.
38+
if (token.indexOf('\'') != -1) token = token.replace("'", "\\'");
39+
// Replace other special characters.
40+
token = replaceCommonSpecialCharactersIfPresent(token);
4341

44-
return "['" + t + "']";
42+
return "['" + token + "']";
4543
}, (index) -> "[" + index + "]"),
4644

4745
/**
@@ -51,12 +49,10 @@ public enum PathType {
5149
/*
5250
* Escape '~' with '~0' and '/' with '~1'.
5351
*/
54-
if (token.indexOf('~') != -1) {
55-
token = token.replace("~", "~0");
56-
}
57-
if (token.indexOf('/') != -1) {
58-
token = token.replace("/", "~1");
59-
}
52+
if (token.indexOf('~') != -1) token = token.replace("~", "~0");
53+
if (token.indexOf('/') != -1) token = token.replace("/", "~1");
54+
// Replace other special characters.
55+
token = replaceCommonSpecialCharactersIfPresent(token);
6056
return "/" + token;
6157
}, (index) -> "/" + index);
6258

@@ -81,6 +77,21 @@ public enum PathType {
8177
this.appendIndexFn = appendIndexFn;
8278
}
8379

80+
/**
81+
* Replace common special characters that are to be considered for all types of paths.
82+
*
83+
* @param token The path token (property name or selector).
84+
* @return The token to use in the path.
85+
*/
86+
private static String replaceCommonSpecialCharactersIfPresent(String token) {
87+
if (token.indexOf('\n') != -1) token = token.replace("\n", "\\n");
88+
if (token.indexOf('\t') != -1) token = token.replace("\t", "\\t");
89+
if (token.indexOf('\r') != -1) token = token.replace("\r", "\\r");
90+
if (token.indexOf('\b') != -1) token = token.replace("\b", "\\b");
91+
if (token.indexOf('\f') != -1) token = token.replace("\f", "\\f");
92+
return token;
93+
}
94+
8495
/**
8596
* Append the given child token to the provided current path.
8697
*

src/test/java/com/networknt/schema/Issue687Test.java

Lines changed: 24 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -91,68 +91,44 @@ void testValidationMessage(PathType pathType, String schemaPath, String content,
9191
}
9292
}
9393

94-
@Test
95-
void testDoubleQuotes() throws JsonProcessingException {
96-
ObjectMapper mapper = new ObjectMapper();
97-
SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
98-
schemaValidatorsConfig.setPathType(PathType.JSON_PATH);
99-
/*
100-
{
101-
"$schema": "https://json-schema.org/draft/2019-09/schema",
102-
"type": "object",
103-
"properties": {
104-
"\"": {
105-
"type": "boolean"
106-
}
107-
}
108-
}
109-
*/
110-
JsonSchema schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)
111-
.getSchema(mapper.readTree("{\n" +
112-
" \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n" +
113-
" \"type\": \"object\",\n" +
114-
" \"properties\": {\n" +
115-
" \"\\\"\": {\n" +
116-
" \"type\": \"boolean\"\n" +
117-
" }\n" +
118-
" }\n" +
119-
"}"), schemaValidatorsConfig);
120-
// {"\"": 1}
121-
Set<ValidationMessage> validationMessages = schema.validate(mapper.readTree("{\"\\\"\": 1}"));
122-
assertEquals(1, validationMessages.size());
123-
assertEquals("$['\"']", validationMessages.iterator().next().getPath());
94+
public static Stream<Arguments> specialCharacterTests() {
95+
return Stream.of(
96+
Arguments.of(PathType.JSON_PATH, "'", "$['\\'']"),
97+
Arguments.of(PathType.JSON_PATH, "\\\"", "$['\"']"),
98+
Arguments.of(PathType.JSON_PATH, "\\n", "$['\\n']"),
99+
Arguments.of(PathType.JSON_PATH, "\\r", "$['\\r']"),
100+
Arguments.of(PathType.JSON_PATH, "\\t", "$['\\t']"),
101+
Arguments.of(PathType.JSON_PATH, "\\f", "$['\\f']"),
102+
Arguments.of(PathType.JSON_PATH, "\\b", "$['\\b']"),
103+
Arguments.of(PathType.JSON_POINTER, "~", "/~0"),
104+
Arguments.of(PathType.JSON_POINTER, "/", "/~1"),
105+
Arguments.of(PathType.JSON_POINTER, "\\n", "/\\n"),
106+
Arguments.of(PathType.JSON_POINTER, "\\r", "/\\r"),
107+
Arguments.of(PathType.JSON_POINTER, "\\t", "/\\t"),
108+
Arguments.of(PathType.JSON_POINTER, "\\f", "/\\f"),
109+
Arguments.of(PathType.JSON_POINTER, "\\b", "/\\b")
110+
);
124111
}
125112

126-
@Test
127-
void testSingleQuotes() throws JsonProcessingException {
113+
@ParameterizedTest
114+
@MethodSource("specialCharacterTests")
115+
void testSpecialCharacters(PathType pathType, String propertyName, String expectedPath) throws JsonProcessingException {
128116
ObjectMapper mapper = new ObjectMapper();
129117
SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
130-
schemaValidatorsConfig.setPathType(PathType.JSON_PATH);
131-
/*
132-
{
133-
"$schema": "https://json-schema.org/draft/2019-09/schema",
134-
"type": "object",
135-
"properties": {
136-
"'": {
137-
"type": "boolean"
138-
}
139-
}
140-
}
141-
*/
118+
schemaValidatorsConfig.setPathType(pathType);
142119
JsonSchema schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909)
143120
.getSchema(mapper.readTree("{\n" +
144121
" \"$schema\": \"https://json-schema.org/draft/2019-09/schema\",\n" +
145122
" \"type\": \"object\",\n" +
146123
" \"properties\": {\n" +
147-
" \"'\": {\n" +
124+
" \""+propertyName+"\": {\n" +
148125
" \"type\": \"boolean\"\n" +
149126
" }\n" +
150127
" }\n" +
151128
"}"), schemaValidatorsConfig);
152-
// {"\"": 1}
153-
Set<ValidationMessage> validationMessages = schema.validate(mapper.readTree("{\"'\": 1}"));
129+
Set<ValidationMessage> validationMessages = schema.validate(mapper.readTree("{\""+propertyName+"\": 1}"));
154130
assertEquals(1, validationMessages.size());
155-
assertEquals("$['\\'']", validationMessages.iterator().next().getPath());
131+
assertEquals(expectedPath, validationMessages.iterator().next().getPath());
156132
}
157133

158134
}

0 commit comments

Comments
 (0)