AposUnescapedControlChars1750Test.java

package tools.jackson.core.unittest.read;

import org.junit.jupiter.api.Test;

import tools.jackson.core.*;
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#1750]: {@code ALLOW_UNESCAPED_CONTROL_CHARS} must
 * apply to single-quoted string values in all parsers.
 */
class AposUnescapedControlChars1750Test extends JacksonCoreTestBase
{
    private final JsonFactory APOS_F = JsonFactory.builder()
            .enable(JsonReadFeature.ALLOW_SINGLE_QUOTES)
            .build();

    private final JsonFactory APOS_CTRL_F = APOS_F.rebuild()
            .enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS)
            .build();

    private final static String[] VALUES = new String[] {
            "a\tb", "\t", "a\u0001b", "x\ny\rz", "��\t���", "\t\uD83D\uDE00\t",
            _longValue()
    };

    @Test
    void controlCharsInAposValueAllowed() throws Exception
    {
        for (int mode : ALL_MODES) {
            for (String value : VALUES) {
                try (JsonParser p = createParser(APOS_CTRL_F, mode, "['" + value + "']")) {
                    assertToken(JsonToken.START_ARRAY, p.nextToken());
                    assertToken(JsonToken.VALUE_STRING, p.nextToken());
                    assertEquals(value, p.getString(), "mode " + mode);
                    assertToken(JsonToken.END_ARRAY, p.nextToken());
                }
                try (JsonParser p = createParser(APOS_CTRL_F, mode, "{'a':'" + value + "'}")) {
                    assertToken(JsonToken.START_OBJECT, p.nextToken());
                    assertToken(JsonToken.PROPERTY_NAME, p.nextToken());
                    assertToken(JsonToken.VALUE_STRING, p.nextToken());
                    assertEquals(value, p.getString(), "mode " + mode);
                    assertToken(JsonToken.END_OBJECT, p.nextToken());
                }
                // and skipping (no access to text)
                try (JsonParser p = createParser(APOS_CTRL_F, mode, "['" + value + "',true]")) {
                    assertToken(JsonToken.START_ARRAY, p.nextToken());
                    assertToken(JsonToken.VALUE_STRING, p.nextToken());
                    assertToken(JsonToken.VALUE_TRUE, p.nextToken());
                    assertToken(JsonToken.END_ARRAY, p.nextToken());
                }
            }
        }
    }

    @Test
    void controlCharsInAposNameAllowed() throws Exception
    {
        for (int mode : ALL_MODES) {
            for (String name : new String[] { "a\tb", "\t", "x\ny" }) {
                try (JsonParser p = createParser(APOS_CTRL_F, mode, "{'" + name + "':1}")) {
                    assertToken(JsonToken.START_OBJECT, p.nextToken());
                    assertToken(JsonToken.PROPERTY_NAME, p.nextToken());
                    assertEquals(name, p.currentName(), "mode " + mode);
                    assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
                    assertToken(JsonToken.END_OBJECT, p.nextToken());
                }
            }
        }
    }

    @Test
    void controlCharsInAposDisallowed() throws Exception
    {
        for (int mode : ALL_MODES) {
            for (String doc : new String[] { "['a\tb']", "{'a':'a\tb'}", "['" + _longValue() + "']" }) {
                try (JsonParser p = createParser(APOS_F, mode, doc)) {
                    p.nextToken();
                    p.nextToken();
                    p.nextToken();
                    p.getString();
                    fail("Should not pass, mode " + mode);
                } catch (StreamReadException e) {
                    verifyException(e, "Illegal unquoted character");
                    verifyException(e, "string value");
                }
            }
            try (JsonParser p = createParser(APOS_F, mode, "{'a\tb':1}")) {
                p.nextToken();
                p.nextToken();
                fail("Should not pass, mode " + mode);
            } catch (StreamReadException e) {
                verifyException(e, "Illegal unquoted character");
                verifyException(e, "name");
            }
        }
    }

    // Linefeeds in String values must update row number
    @Test
    void linefeedsUpdateRow() throws Exception
    {
        for (int mode : ALL_MODES) {
            for (String lf : new String[] { "\n", "\r", "\r\n" }) {
                // DataInput counts both '\r' and '\n' (no lookahead), same as with white space
                final int expRow = (mode == MODE_DATA_INPUT && lf.length() == 2) ? 3 : 2;
                for (char q : new char[] { '\'', '"' }) {
                    final String doc = "[" + q + "x" + lf + "y" + q + ", 1]";
                    // with and without accessing String value (decode vs skip)
                    for (boolean getText : new boolean[] { true, false }) {
                        try (JsonParser p = createParser(APOS_CTRL_F, mode, doc)) {
                            assertToken(JsonToken.START_ARRAY, p.nextToken());
                            assertToken(JsonToken.VALUE_STRING, p.nextToken());
                            if (getText) {
                                assertEquals("x" + lf + "y", p.getString());
                            }
                            assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
                            assertEquals(expRow, p.currentTokenLocation().getLineNr(),
                                    "mode " + mode + ", doc " + doc.replace("\r", "\\r").replace("\n", "\\n"));
                        }
                    }
                }
            }
        }
    }

    // Allowed control chars must be retained when streaming String value to Writer
    @Test
    void controlCharsStreamedToWriter() throws Exception
    {
        for (int mode : ALL_MODES) {
            for (String value : VALUES) {
                for (char q : new char[] { '\'', '"' }) {
                    try (JsonParser p = createParser(APOS_CTRL_F, mode, "[" + q + value + q + ",1]")) {
                        assertToken(JsonToken.START_ARRAY, p.nextToken());
                        assertToken(JsonToken.VALUE_STRING, p.nextToken());
                        java.io.StringWriter w = new java.io.StringWriter();
                        assertEquals(value.length(), p.readString(w), "mode " + mode);
                        assertEquals(value, w.toString(), "mode " + mode);
                        assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
                    }
                }
            }
        }
    }

    // Long enough to span input buffer and text buffer segment boundaries,
    // with control chars at varying offsets
    private static String _longValue() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 6000; ++i) {
            switch (i % 7) {
            case 0:
                sb.append('\t');
                break;
            case 3:
                sb.append('��');
                break;
            default:
                sb.append((char) ('a' + (i % 26)));
            }
        }
        return sb.toString();
    }
}