Skip to content
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

Fixed stack overflow for huge properties with many escaped double quotes #712

Merged
merged 1 commit into from
Feb 12, 2020
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 @@ -18,23 +18,36 @@
public final class JsonBodyFilters {

/*language=RegExp*/
private static final String BOOLEAN = "(?:true|false)";
private static final String BOOLEAN_PATTERN = "(?:true|false)";

/*language=RegExp*/
private static final String NUMBER =
private static final String NUMBER_PATTERN =
"(?:-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)";

private static final Pattern NUMBER = pattern(NUMBER_PATTERN);

/**
* @see <a href="https://stackoverflow.com/a/43597014/232539">Regex for quoted string with escaping quotes</a>
*/
/*language=RegExp*/
private static final String STRING = "(?:\"(?:[^\"\\\\]+|\\\\.)*\")";
private static final String STRING_PATTERN = "(?:\"(.*?[^\\\\])??((\\\\\\\\)+)?+\")";

private static final Pattern STRING = pattern(STRING_PATTERN);

/*language=RegExp*/
private static final String PRIMITIVE =
"(?:" + BOOLEAN + "|" + NUMBER + "|" + STRING + ")";
private static final String PRIMITIVE_PATTERN =
"(?:" + BOOLEAN_PATTERN + "|" + NUMBER_PATTERN + "|" + STRING_PATTERN + ")";

private static final Pattern PRIMITIVE = pattern(PRIMITIVE_PATTERN);

private JsonBodyFilters() {

}

private static Pattern pattern(final String value) {
return compile("(?<key>\"(?<property>.*?)\"\\s*:\\s*)(" + value + "|null)");
}

@API(status = MAINTAINED)
public static BodyFilter accessToken() {
final Set<String> properties = new HashSet<>(Arrays.asList(
Expand Down Expand Up @@ -99,10 +112,9 @@ public static String quote(final String s) {

private static BodyFilter replace(
final Predicate<String> predicate,
final String value,
final Pattern pattern,
final String replacement) {

final Pattern pattern = compile("(?<key>\"(?<property>.*?)\"\\s*:\\s*)(" + value + "|null)");
final UnaryOperator<String> delegate = body -> {
final Matcher matcher = pattern.matcher(body);
final StringBuffer result = new StringBuffer(body.length());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,26 @@ void supportsVeryLargeValues() throws IOException {
assertThat(actual, containsString("\"password\": \"XXX\""));
}

@Test
void supportsValuesWithManyEscapedDoubleQuotes() throws IOException {
final byte[] bytes = Files.readAllBytes(Paths.get("src/test/resources/many-quotes.json"));
final String original = new String(bytes, UTF_8);

final BodyFilter unit = replaceJsonStringProperty(singleton("password"), "XXX");
final String actual = unit.filter(contentType, original);

assertThat(actual, is(original));
}

@Test
void supportsVeryLargeEmbeddedJsonValues() throws IOException {
final byte[] bytes = Files.readAllBytes(Paths.get("src/test/resources/huge-json-value.json"));
final String original = new String(bytes, UTF_8);

final BodyFilter unit = replaceJsonStringProperty(singleton("variables"), "XXX");
final String actual = unit.filter(contentType, original);

assertThat(actual, is(original));
}

}
4 changes: 4 additions & 0 deletions logbook-json/src/test/resources/huge-json-value.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"variables": {},
"query": "\n query IntrospectionQuery {\n __schema {\n queryType { name }\n mutationType { name }\n subscriptionType { name }\n types {\n ...FullType\n }\n directives {\n name\n description\n locations\n args {\n ...InputValue\n }\n }\n }\n }\n\n fragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n }\n\n fragment InputValue on __InputValue {\n name\n description\n type { ...TypeRef }\n defaultValue\n }\n\n fragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n }\n "
}
Loading