EscapedMultiByteChar1756Test.java

package tools.jackson.core.unittest.read;

import org.junit.jupiter.api.Test;

import tools.jackson.core.JsonParser;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.json.JsonFactory;
import tools.jackson.core.json.JsonReadFeature;
import tools.jackson.core.unittest.JacksonCoreTestBase;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

/**
 * Tests for [jackson-core#1756]: blocking byte-based parsers must reject
 * ill-formed (overlong, invalid lead byte) UTF-8 after a backslash, instead of
 * decoding it as a different character; same as non-blocking parsers
 * (see {@code AsyncEscapedMultiByteChar1756Test}).
 */
class EscapedMultiByteChar1756Test extends JacksonCoreTestBase
{
    private final JsonFactory FACTORY = newStreamFactory();

    private final JsonFactory APOS_FACTORY = JsonFactory.builder()
            .enable(JsonReadFeature.ALLOW_SINGLE_QUOTES)
            .build();

    private final JsonFactory ANY_ESCAPE = JsonFactory.builder()
            .enable(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER)
            .build();

    // U+1F600 GRINNING FACE
    private static final String SMILEY = "����";

    @Test
    void escapedOverlongUTF8Rejected() throws Exception
    {
        for (int mode : ALL_BINARY_MODES) {
            // 0xC0 and 0xC1 can only start overlong 2-byte encodings
            for (JsonFactory f : new JsonFactory[] { FACTORY, ANY_ESCAPE }) {
                _testBroken(f, mode, utf8Bytes("[\"\\", new int[] { 0xC0, 0xA2 }, "\"]"),
                        "Invalid UTF-8 start byte 0xc0");
                _testBroken(f, mode, utf8Bytes("{\"\\", new int[] { 0xC1, 0x9C }, "\":1}"),
                        "Invalid UTF-8 start byte 0xc1");
            }
            // 3-byte encoding of '"', 4-byte encodings of '\'' and NUL
            _testBroken(ANY_ESCAPE, mode, utf8Bytes("[\"\\", new int[] { 0xE0, 0x80, 0xA2 }, "\"]"),
                    "Invalid UTF-8: overlong encoding (lead byte 0xe0, second byte 0x80)");
            _testBroken(ANY_ESCAPE, mode, utf8Bytes("{\"\\", new int[] { 0xE0, 0x80, 0xA2 }, "\":1}"),
                    "Invalid UTF-8: overlong encoding (lead byte 0xe0, second byte 0x80)");
            _testBroken(APOS_FACTORY, mode, utf8Bytes("['\\", new int[] { 0xF0, 0x80, 0x80, 0xA7 }, "']"),
                    "Invalid UTF-8: overlong encoding (lead byte 0xf0, second byte 0x80)");
            _testBroken(APOS_FACTORY, mode, utf8Bytes("{'\\", new int[] { 0xF0, 0x80, 0x80, 0xA7 }, "':1}"),
                    "Invalid UTF-8: overlong encoding (lead byte 0xf0, second byte 0x80)");
            _testBroken(ANY_ESCAPE, mode, utf8Bytes("[\"\\", new int[] { 0xF0, 0x80, 0x80, 0x80 }, "\"]"),
                    "Invalid UTF-8: overlong encoding (lead byte 0xf0, second byte 0x80)");
        }
    }

    // 0xF5 - 0xF7 can only start encodings beyond U+10FFFF
    @Test
    void escapedLeadByteBeyondUnicodeRejected() throws Exception
    {
        for (int mode : ALL_BINARY_MODES) {
            for (int lead : new int[] { 0xF5, 0xF6, 0xF7 }) {
                final String exp = "Invalid UTF-8 start byte 0x" + Integer.toHexString(lead);
                _testBroken(ANY_ESCAPE, mode, utf8Bytes("[\"\\", new int[] { lead, 0x80, 0x80, 0x80 }, "\"]"),
                        exp);
                _testBroken(ANY_ESCAPE, mode, utf8Bytes("{\"\\", new int[] { lead, 0x80, 0x80, 0x80 }, "\":1}"),
                        exp);
            }
        }
    }

    // Rejected escaped supplementary character reported at its last byte, like other
    // unrecognized escapes
    // (DataInput-backed parser does not track offsets)
    @Test
    void escapedCharErrorLocation() throws Exception
    {
        _testErrorLocation(ANY_ESCAPE, SMILEY);
        _testErrorLocation(FACTORY, SMILEY);
    }

    private void _testErrorLocation(JsonFactory f, String ch) throws Exception
    {
        for (int mode : new int[] { MODE_INPUT_STREAM, MODE_INPUT_STREAM_THROTTLED }) {
            for (String doc : new String[] { "{\"\\" + ch + "\":1}", "[\"\\" + ch + "\"]" }) {
                try (JsonParser p = createParser(f, mode, utf8Bytes(doc))) {
                    while (p.nextToken() != null) {
                        p.getText();
                    }
                    fail("Should not pass for mode " + mode);
                } catch (StreamReadException e) {
                    verifyException(e, "Unrecognized character escape");
                    assertEquals(6L, e.getLocation().getByteOffset());
                    assertEquals(1, e.getLocation().getLineNr());
                    assertEquals(7, e.getLocation().getColumnNr());
                }
            }
        }
    }

    private void _testBroken(JsonFactory f, int mode, byte[] doc, String expMsg) throws Exception
    {
        try (JsonParser p = createParser(f, mode, doc)) {
            while (p.nextToken() != null) {
                p.getText();
            }
            fail("Should not pass for mode " + mode);
        } catch (StreamReadException e) {
            verifyException(e, expMsg);
        }
    }

}