Skip to content

Commit

Permalink
json: Disallow trailing data by default, close #167
Browse files Browse the repository at this point in the history
  • Loading branch information
TheElectronWill committed May 12, 2024
1 parent fc3df2a commit 07c866f
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public final class JsonParser implements ConfigParser<Config> {

private final ConfigFormat<Config> configFormat;
private boolean emptyDataAccepted = false;
private boolean trailingDataAccepted = false;

public JsonParser() {
this(JsonFormat.fancyInstance());
Expand Down Expand Up @@ -57,6 +58,24 @@ public JsonParser setEmptyDataAccepted(boolean emptyDataAccepted) {
return this;
}

/**
* @return true if the parser accepts trailing data (data after the end of the JSON document) as a valid input, false otherwise (default)
*/
public boolean isTrailingDataAccepted() {
return trailingDataAccepted;
}

/**
* Enables or disables the acceptance of trailing input data. False by default. If set to true,
* the parser will ignore any data that is after the end of the document.
*
* @param trailingDataAccepted true to accept trailing data (data after the end of the JSON document) as a valid input, false to reject it
*/
public JsonParser setTrailingDataAccepted(boolean trailingDataAccepted) {
this.trailingDataAccepted = trailingDataAccepted;
return this;
}

/**
* Parses a JSON document, either a JSON object (parsed to a JsonConfig) or a JSON array
* (parsed to a List).
Expand Down Expand Up @@ -97,13 +116,24 @@ public Object parseDocument(Reader reader, Config configModel) {
}
}
char firstChar = input.readCharAndSkip(SPACES);
Object result;
if (firstChar == '{') {
return parseObject(input, configModel.createSubConfig(), ParsingMode.MERGE);
result =parseObject(input, configModel.createSubConfig(), ParsingMode.MERGE);
} else if (firstChar == '[') {
result = parseArray(input, new ArrayList<>(), ParsingMode.MERGE, configModel.createSubConfig());
} else {
throw new ParsingException("Invalid first character for a json document: " + firstChar);
}
if (firstChar == '[') {
return parseArray(input, new ArrayList<>(), ParsingMode.MERGE, configModel.createSubConfig());

int trailing = input.readAndSkip(SPACES);
if (trailing >= 0) {
input.pushBack((char) trailing);
String msg = String.format(
"Invalid data at the end of the JSON document: %s (use JsonParser.setTrailingDataAccepted(true) if you intend this to work)",
input.read(6).toString());
throw new ParsingException(msg);
}
throw new ParsingException("Invalid first character for a json document: " + firstChar);
return result;
}

/**
Expand Down Expand Up @@ -143,6 +173,14 @@ public void parse(Reader reader, Config destination, ParsingMode parsingMode) {
parsingMode.prepareParsing(destination);
parseObject(input, destination, parsingMode);
}
int trailing = input.readAndSkip(SPACES);
if (trailing >= 0) {
input.pushBack((char) trailing);
String msg = String.format(
"Invalid data at the end of the JSON document: %s (use JsonParser.setTrailingDataAccepted(true) if you intend this to work)",
input.read(6).toString());
throw new ParsingException(msg);
}
}

/**
Expand Down Expand Up @@ -198,6 +236,15 @@ public void parseList(Reader reader, List<?> destination, ParsingMode parsingMod
throw new ParsingException("Invalid first character for a json array: " + firstChar);
}
parseArray(input, destination, parsingMode, configModel);

int trailing = input.readAndSkip(SPACES);
if (trailing >= 0) {
input.pushBack((char) trailing);
String msg = String.format(
"Invalid data at the end of the JSON document: %s (use JsonParser.setTrailingDataAccepted(true) if you intend this to work)",
input.read(6).toString());
throw new ParsingException(msg);
}
}

private <T extends Config> T parseObject(CharacterInput input, T config, ParsingMode parsingMode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@
import com.electronwill.nightconfig.core.concurrent.StampedConfig;
import com.electronwill.nightconfig.core.concurrent.SynchronizedConfig;
import com.electronwill.nightconfig.core.file.FileNotFoundAction;
import com.electronwill.nightconfig.core.io.ParsingException;
import com.electronwill.nightconfig.core.io.ParsingMode;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.File;
import java.util.HashMap;
import java.util.List;

public class JsonParserTest {
@Test
public void read() {
Config config = new JsonParser().parse(new File("test.json"), FileNotFoundAction.THROW_ERROR);
@Test
public void read() {
Config config = new JsonParser().parse(new File("test.json"), FileNotFoundAction.THROW_ERROR);
Util.checkExample(config);
}
}

@Test
@Test
public void readToSynchronizedConfig() {
File f = new File("test.json");
SynchronizedConfig config = new SynchronizedConfig(InMemoryCommentedFormat.defaultInstance(), HashMap::new);
Expand All @@ -34,4 +39,92 @@ public void readToStampedConfig() {
new JsonParser().parse(f, config, ParsingMode.REPLACE, FileNotFoundAction.THROW_ERROR);
Util.checkExample(config);
}

@SuppressWarnings("unchecked")
@Test
public void readSpaced() {
Config config = new JsonParser().parse(" {} ");
assertTrue(config.isEmpty());

config = (Config)new JsonParser().parseDocument(" {} ");
assertTrue(config.isEmpty());

List<Object> array = (List<Object>)new JsonParser().parseDocument(" [] ");
assertTrue(array.isEmpty());

array = (List<Object>)new JsonParser().parseList(" [] ");
assertTrue(array.isEmpty());
}

@Test
public void parseInvalidDocument() {
assertThrows(ParsingException.class, () -> {
new JsonParser().parseDocument("{}abcdefg");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parseDocument("abcdefg{}");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parseDocument("{} \nabcdefg");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parseDocument("[]abcdefg");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parseDocument("[] \nabcdefg");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parseDocument("a");
});
}

@Test
public void parseInvalidObject() {
assertThrows(ParsingException.class, () -> {
new JsonParser().parse("{}abcdefg");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parse("abcdefg{}");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parse("{} \nabcdefg");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parse("[]"); // not an object
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parse("[1,2,3,4]"); // not an object
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parse("[]abcdefg");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parse("[] \nabcdefg");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parse("a");
});
}

@Test
public void parseInvalidList() {
assertThrows(ParsingException.class, () -> {
new JsonParser().parseList("[]abcdefg");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parseList("abcdefg[]");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parseList("{}"); // not a list
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parseList("[]abcdefg");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parseList("[] \nabcdefg");
});
assertThrows(ParsingException.class, () -> {
new JsonParser().parseList("a");
});
}
}

0 comments on commit 07c866f

Please sign in to comment.