DataInputInvalidToken1752Test.java
package tools.jackson.core.unittest.read;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.core.exc.JacksonIOException;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.exc.UnexpectedEndOfInputException;
import tools.jackson.core.json.JsonFactory;
import tools.jackson.core.unittest.JacksonCoreTestBase;
import static org.junit.jupiter.api.Assertions.*;
// [core#1752]: invalid token reporting with DataInput must neither swallow
// I/O failures nor report end-of-input as I/O failure
class DataInputInvalidToken1752Test
extends JacksonCoreTestBase
{
private final JsonFactory JSON_F = newStreamFactory();
@Test
void invalidTokenPreservesReadFailure() throws Exception
{
final IOException failure = new IOException("test-induced read failure");
try (JsonParser p = createParserForDataInput(JSON_F,
new DataInputStream(_failAfter("a", failure)))) {
JacksonIOException e = assertThrows(JacksonIOException.class, p::nextToken);
verifyException(e, failure.getMessage());
assertSame(failure, e.getCause());
assertSame(p, e.processor());
}
}
@Test
void base64PreservesReadFailure() throws Exception
{
final IOException failure = new IOException("test-induced read failure");
try (JsonParser p = createParserForDataInput(JSON_F,
new DataInputStream(_failAfter("\"AQID", failure)))) {
assertToken(JsonToken.VALUE_STRING, p.nextToken());
JacksonIOException e = assertThrows(JacksonIOException.class,
() -> p.getBinaryValue());
assertSame(failure, e.getCause());
}
}
@Test
void readBinaryPreservesReadFailure() throws Exception
{
final IOException failure = new IOException("test-induced read failure");
try (JsonParser p = createParserForDataInput(JSON_F,
new DataInputStream(_failAfter("\"AQID", failure)))) {
assertToken(JsonToken.VALUE_STRING, p.nextToken());
JacksonIOException e = assertThrows(JacksonIOException.class,
() -> p.readBinaryValue(new ByteArrayOutputStream()));
assertSame(failure, e.getCause());
}
}
@Test
void keywordPreservesReadFailure() throws Exception
{
_testReadFailure(_failAfter("tr", new IOException("test-induced read failure")));
}
@Test
void truncatedUtf8PreservesReadFailure() throws Exception
{
// lead byte of 2-byte char after keyword, then failure instead of continuation byte
_testReadFailure(_failAfter("true", new IOException("test-induced read failure"),
(byte) 0xC3));
}
@Test
void base64TruncatedAtEOF() throws Exception
{
for (int mode : ALL_BINARY_MODES) {
try (JsonParser p = createParser(JSON_F, mode, utf8Bytes("\"AQID"))) {
assertToken(JsonToken.VALUE_STRING, p.nextToken());
assertThrows(UnexpectedEndOfInputException.class, () -> p.getBinaryValue());
}
}
}
@Test
void readBinaryTruncatedAtEOF() throws Exception
{
for (int mode : ALL_BINARY_MODES) {
try (JsonParser p = createParser(JSON_F, mode, utf8Bytes("\"AQID"))) {
assertToken(JsonToken.VALUE_STRING, p.nextToken());
assertThrows(UnexpectedEndOfInputException.class,
() -> p.readBinaryValue(new ByteArrayOutputStream()));
}
}
}
@Test
void invalidTokenAtEOF() throws Exception
{
_testInvalidToken("abc", "abc");
_testInvalidToken("tru", "tru");
_testInvalidToken("truea", "truea");
_testInvalidToken("nullx", "nullx");
_testInvalidToken("falsez", "falsez");
_testInvalidToken("��", "��");
_testInvalidToken("[ tr", "tr");
}
@Test
void invalidTokenMalformedUtf8() throws Exception
{
for (int mode : ALL_BINARY_MODES) {
try (JsonParser p = createParser(JSON_F, mode, new byte[] { 'a', (byte) 0xC2, 'A' })) {
StreamReadException e = assertThrows(StreamReadException.class, p::nextToken);
verifyException(e, "Invalid UTF-8 middle byte 0x41");
}
}
}
@Test
void invalidTokenTruncatedUtf8() throws Exception
{
// Multi-byte char following invalid token: token itself still reported
try (JsonParser p = createParserForDataInput(JSON_F,
new DataInputStream(new ByteArrayInputStream(new byte[] { 'a', (byte) 0xC2 })))) {
StreamReadException e = assertThrows(StreamReadException.class, p::nextToken);
verifyException(e, "Unrecognized token 'a'");
}
}
@Test
void truncatedUtf8AtEOF() throws Exception
{
_testTruncated(new byte[] { 't', 'r', 'u', 'e', (byte) 0xC3 });
_testTruncated(new byte[] { (byte) 0xC3 });
_testTruncated(new byte[] { '{', (byte) 0xC3 });
try (JsonParser p = createParser(JSON_F, MODE_DATA_INPUT, new byte[] { '{', (byte) 0xC3 })) {
assertToken(JsonToken.START_OBJECT, p.nextToken());
UnexpectedEndOfInputException e = assertThrows(UnexpectedEndOfInputException.class,
p::nextToken);
assertEquals(JsonToken.PROPERTY_NAME, e.getTokenBeingDecoded());
}
}
private void _testReadFailure(InputStream in) throws Exception
{
try (JsonParser p = createParserForDataInput(JSON_F, new DataInputStream(in))) {
JacksonIOException e = assertThrows(JacksonIOException.class, () -> {
while (p.nextToken() != null) { }
});
verifyException(e, "test-induced read failure");
}
}
private void _testInvalidToken(String doc, String token) throws Exception
{
for (int mode : ALL_BINARY_MODES) {
try (JsonParser p = createParser(JSON_F, mode, utf8Bytes(doc))) {
StreamReadException e = assertThrows(StreamReadException.class, () -> {
while (p.nextToken() != null) { }
});
verifyException(e, "Unrecognized token '"+token+"'");
}
}
}
private void _testTruncated(byte[] doc) throws Exception
{
for (int mode : ALL_BINARY_MODES) {
try (JsonParser p = createParser(JSON_F, mode, doc)) {
UnexpectedEndOfInputException e = assertThrows(UnexpectedEndOfInputException.class, () -> {
while (p.nextToken() != null) { }
});
if (mode == MODE_DATA_INPUT) {
verifyException(e, "in a multi-byte UTF-8 character");
}
}
}
}
private static InputStream _failAfter(String content, IOException failure, byte... extra) {
final byte[] str = content.getBytes(StandardCharsets.UTF_8);
final byte[] data = Arrays.copyOf(str, str.length + extra.length);
System.arraycopy(extra, 0, data, str.length, extra.length);
return new InputStream() {
private int _offset;
@Override
public int read() throws IOException {
if (_offset < data.length) {
return data[_offset++] & 0xFF;
}
throw failure;
}
};
}
}