Skip to content

feat: GsonFactory to have read leniency option #1819

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
merged 9 commits into from
Feb 24, 2023
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 @@ -52,12 +52,23 @@ public static GsonFactory getDefaultInstance() {
return InstanceHolder.INSTANCE;
}

/** Controls the behavior of leniency in reading JSON value */
private boolean readLeniency = false;

/** Holder for the result of {@link #getDefaultInstance()}. */
@Beta
static class InstanceHolder {
static final GsonFactory INSTANCE = new GsonFactory();
}

// Keeping the default, non-arg constructor for backward compatibility. Users should use
// getDefaultInstance() or builder() instead.
public GsonFactory() {}

private GsonFactory(Builder builder) {
readLeniency = builder.readLeniency;
}

@Override
public JsonParser createJsonParser(InputStream in) {
return createJsonParser(new InputStreamReader(in, StandardCharsets.UTF_8));
Expand Down Expand Up @@ -90,4 +101,36 @@ public JsonGenerator createJsonGenerator(OutputStream out, Charset enc) {
public JsonGenerator createJsonGenerator(Writer writer) {
return new GsonGenerator(this, new JsonWriter(writer));
}

/** Returns true if it is lenient to input JSON value. */
boolean getReadLeniency() {
return readLeniency;
}

/** Returns the builder * */
public static Builder builder() {
return new Builder();
}

/** Builder for GsonFactory. */
public static final class Builder {
// Do not directly call this constructor
private Builder() {}

private boolean readLeniency = false;

/**
* Set to {@code true} when you want to the JSON parser to be lenient to reading JSON value. By
* default, it is {@code false}.
*/
public Builder setReadLeniency(boolean readLeniency) {
this.readLeniency = readLeniency;
return this;
}

/** Builds GsonFactory instance. */
public GsonFactory build() {
return new GsonFactory(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class GsonParser extends JsonParser {
GsonParser(GsonFactory factory, JsonReader reader) {
this.factory = factory;
this.reader = reader;
reader.setLenient(false);
reader.setLenient(factory.getReadLeniency());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@

package com.google.api.client.json.gson;

import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.JsonParser;
import com.google.api.client.test.json.AbstractJsonFactoryTest;
import com.google.gson.stream.MalformedJsonException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;

/**
Expand Down Expand Up @@ -94,4 +100,30 @@ public final void testGetByteValue() throws IOException {
assertNotNull(ex.getMessage());
}
}

public final void testReaderLeniency_lenient() throws IOException {
JsonObjectParser parser =
new JsonObjectParser(GsonFactory.builder().setReadLeniency(true).build());
Copy link
Member Author

Choose a reason for hiding this comment

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

Memo: This is how to create GsonFactory with readLeniency true.


// This prefix in JSON body is used to prevent Cross-site script inclusion (XSSI).
InputStream inputStream =
new ByteArrayInputStream((")]}'\n" + JSON_ENTRY_PRETTY).getBytes(StandardCharsets.UTF_8));
GenericJson json = parser.parseAndClose(inputStream, StandardCharsets.UTF_8, GenericJson.class);

assertEquals("foo", json.get("title"));
}

public final void testReaderLeniency_not_lenient_by_default() throws IOException {
JsonObjectParser parser = new JsonObjectParser(GsonFactory.getDefaultInstance());

try {
// This prefix in JSON body is used to prevent Cross-site script inclusion (XSSI).
InputStream inputStream =
new ByteArrayInputStream((")]}'\n" + JSON_ENTRY_PRETTY).getBytes(StandardCharsets.UTF_8));
parser.parseAndClose(inputStream, StandardCharsets.UTF_8, GenericJson.class);
fail("The read leniency should fail the JSON input with XSSI prefix.");
} catch (MalformedJsonException ex) {
assertNotNull(ex.getMessage());
}
}
}