Skip to content

Commit 2a0054a

Browse files
authored
Add NonBlockingByteBufferParser tests for JSON-escaped surrogate pairs in field names (#1581) (#1582)
1 parent f97baf9 commit 2a0054a

File tree

1 file changed

+290
-0
lines changed

1 file changed

+290
-0
lines changed
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
package tools.jackson.core.unittest.json.async;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import tools.jackson.core.*;
6+
import tools.jackson.core.exc.StreamReadException;
7+
import tools.jackson.core.json.JsonFactory;
8+
import tools.jackson.core.json.JsonReadFeature;
9+
import tools.jackson.core.unittest.async.AsyncTestBase;
10+
import tools.jackson.core.unittest.testutil.AsyncReaderWrapper;
11+
12+
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
import static org.junit.jupiter.api.Assertions.fail;
14+
15+
/**
16+
* ByteBuffer async parser tests for [jackson-core#1581]: JSON-escaped surrogate pairs
17+
* (e.g. {@code \ud83d\udc4d}) in field names should work for NonBlockingByteBufferParser,
18+
* same as they do for NonBlockingByteArrayParser (tested in
19+
* {@link AsyncEscapedSurrogateInFieldName1541Test}).
20+
*/
21+
class AsyncByteBufferEscapedSurrogateInFieldName1581Test extends AsyncTestBase
22+
{
23+
private final JsonFactory FACTORY = newStreamFactory();
24+
25+
private final JsonFactory APOS_FACTORY = JsonFactory.builder()
26+
.enable(JsonReadFeature.ALLOW_SINGLE_QUOTES)
27+
.build();
28+
29+
// U+1F44D THUMBS UP SIGN = \ud83d\udc4d (UTF-8: F0 9F 91 8D)
30+
private static final String THUMBS_UP = "\uD83D\uDC4D";
31+
32+
// U+1D11E MUSICAL SYMBOL G CLEF = \ud834\udd1e (UTF-8: F0 9D 84 9E)
33+
private static final String G_CLEF = "\uD834\uDD1E";
34+
35+
// Escaped form of surrogate pairs for use in JSON
36+
private static final String ESC_THUMBS = "\\ud83d\\udc4d";
37+
private static final String ESC_GCLEF = "\\ud834\\udd1e";
38+
39+
/*
40+
/**********************************************************************
41+
/* Test methods, success cases with various bytesPerRead
42+
/**********************************************************************
43+
*/
44+
45+
@Test
46+
void surrogateInFieldNameByteBuffer1Byte() throws Exception
47+
{
48+
_testSurrogateInFieldNameByteBuffer(1);
49+
}
50+
51+
@Test
52+
void surrogateInFieldNameByteBuffer2Bytes() throws Exception
53+
{
54+
_testSurrogateInFieldNameByteBuffer(2);
55+
}
56+
57+
@Test
58+
void surrogateInFieldNameByteBuffer3Bytes() throws Exception
59+
{
60+
_testSurrogateInFieldNameByteBuffer(3);
61+
}
62+
63+
@Test
64+
void surrogateInFieldNameByteBuffer7Bytes() throws Exception
65+
{
66+
_testSurrogateInFieldNameByteBuffer(7);
67+
}
68+
69+
@Test
70+
void surrogateInFieldNameByteBuffer100Bytes() throws Exception
71+
{
72+
_testSurrogateInFieldNameByteBuffer(100);
73+
}
74+
75+
private void _testSurrogateInFieldNameByteBuffer(int bytesPerRead) throws Exception
76+
{
77+
_testFieldNameByteBuffer(FACTORY, bytesPerRead, ESC_THUMBS, THUMBS_UP);
78+
}
79+
80+
/*
81+
/**********************************************************************
82+
/* Test methods, apostrophe-quoted field names
83+
/**********************************************************************
84+
*/
85+
86+
@Test
87+
void surrogateInAposFieldNameByteBuffer1Byte() throws Exception
88+
{
89+
_testSurrogateInAposFieldNameByteBuffer(1);
90+
}
91+
92+
@Test
93+
void surrogateInAposFieldNameByteBuffer3Bytes() throws Exception
94+
{
95+
_testSurrogateInAposFieldNameByteBuffer(3);
96+
}
97+
98+
@Test
99+
void surrogateInAposFieldNameByteBuffer100Bytes() throws Exception
100+
{
101+
_testSurrogateInAposFieldNameByteBuffer(100);
102+
}
103+
104+
private void _testSurrogateInAposFieldNameByteBuffer(int bytesPerRead) throws Exception
105+
{
106+
_testAposFieldNameByteBuffer(bytesPerRead, ESC_THUMBS, THUMBS_UP);
107+
}
108+
109+
/*
110+
/**********************************************************************
111+
/* Test methods, name variations (quad boundaries, long names)
112+
/**********************************************************************
113+
*/
114+
115+
@Test
116+
void nameVariationsByteBuffer1Byte() throws Exception
117+
{
118+
_testNameVariationsByteBuffer(1);
119+
}
120+
121+
@Test
122+
void nameVariationsByteBuffer3Bytes() throws Exception
123+
{
124+
_testNameVariationsByteBuffer(3);
125+
}
126+
127+
@Test
128+
void nameVariationsByteBuffer100Bytes() throws Exception
129+
{
130+
_testNameVariationsByteBuffer(100);
131+
}
132+
133+
private void _testNameVariationsByteBuffer(int bytesPerRead) throws Exception
134+
{
135+
// Prefix lengths 0-5 (varying quad offset when escape is hit)
136+
_testFieldNameByteBuffer(FACTORY, bytesPerRead, ESC_THUMBS, THUMBS_UP);
137+
_testFieldNameByteBuffer(FACTORY, bytesPerRead, "a" + ESC_THUMBS, "a" + THUMBS_UP);
138+
_testFieldNameByteBuffer(FACTORY, bytesPerRead, "ab" + ESC_THUMBS, "ab" + THUMBS_UP);
139+
_testFieldNameByteBuffer(FACTORY, bytesPerRead, "abc" + ESC_THUMBS, "abc" + THUMBS_UP);
140+
_testFieldNameByteBuffer(FACTORY, bytesPerRead, "abcd" + ESC_THUMBS, "abcd" + THUMBS_UP);
141+
_testFieldNameByteBuffer(FACTORY, bytesPerRead, "abcde" + ESC_THUMBS, "abcde" + THUMBS_UP);
142+
143+
// Suffix after surrogate pair
144+
_testFieldNameByteBuffer(FACTORY, bytesPerRead, ESC_THUMBS + "z", THUMBS_UP + "z");
145+
146+
// Sandwiched: ASCII + surrogate + ASCII
147+
_testFieldNameByteBuffer(FACTORY, bytesPerRead,
148+
"x" + ESC_THUMBS + "y", "x" + THUMBS_UP + "y");
149+
150+
// Two consecutive surrogate pairs
151+
_testFieldNameByteBuffer(FACTORY, bytesPerRead,
152+
ESC_THUMBS + ESC_THUMBS, THUMBS_UP + THUMBS_UP);
153+
154+
// Two different supplementary characters
155+
_testFieldNameByteBuffer(FACTORY, bytesPerRead,
156+
ESC_THUMBS + ESC_GCLEF, THUMBS_UP + G_CLEF);
157+
158+
// Different supplementary character alone
159+
_testFieldNameByteBuffer(FACTORY, bytesPerRead, ESC_GCLEF, G_CLEF);
160+
161+
// Long prefix (>12 bytes)
162+
_testFieldNameByteBuffer(FACTORY, bytesPerRead,
163+
"abcdefghijklm" + ESC_THUMBS,
164+
"abcdefghijklm" + THUMBS_UP);
165+
166+
// Long prefix + suffix
167+
_testFieldNameByteBuffer(FACTORY, bytesPerRead,
168+
"abcdefghijklm" + ESC_THUMBS + "n",
169+
"abcdefghijklm" + THUMBS_UP + "n");
170+
171+
// Long name with surrogate in the middle and more ASCII after
172+
_testFieldNameByteBuffer(FACTORY, bytesPerRead,
173+
"abcdefgh" + ESC_THUMBS + "ijklmnop",
174+
"abcdefgh" + THUMBS_UP + "ijklmnop");
175+
176+
// Long name with multiple different surrogates
177+
_testFieldNameByteBuffer(FACTORY, bytesPerRead,
178+
"abcdefgh" + ESC_THUMBS + "ij" + ESC_GCLEF + "klmn",
179+
"abcdefgh" + THUMBS_UP + "ij" + G_CLEF + "klmn");
180+
}
181+
182+
@Test
183+
void nameVariationsAposByteBuffer1Byte() throws Exception
184+
{
185+
_testNameVariationsAposByteBuffer(1);
186+
}
187+
188+
@Test
189+
void nameVariationsAposByteBuffer100Bytes() throws Exception
190+
{
191+
_testNameVariationsAposByteBuffer(100);
192+
}
193+
194+
private void _testNameVariationsAposByteBuffer(int bytesPerRead) throws Exception
195+
{
196+
// Prefix lengths 0-4
197+
_testAposFieldNameByteBuffer(bytesPerRead, ESC_THUMBS, THUMBS_UP);
198+
_testAposFieldNameByteBuffer(bytesPerRead, "a" + ESC_THUMBS, "a" + THUMBS_UP);
199+
_testAposFieldNameByteBuffer(bytesPerRead, "ab" + ESC_THUMBS, "ab" + THUMBS_UP);
200+
_testAposFieldNameByteBuffer(bytesPerRead, "abc" + ESC_THUMBS, "abc" + THUMBS_UP);
201+
_testAposFieldNameByteBuffer(bytesPerRead, "abcd" + ESC_THUMBS, "abcd" + THUMBS_UP);
202+
203+
// Suffix, sandwiched, multiple
204+
_testAposFieldNameByteBuffer(bytesPerRead, ESC_THUMBS + "z", THUMBS_UP + "z");
205+
_testAposFieldNameByteBuffer(bytesPerRead,
206+
"x" + ESC_THUMBS + "y", "x" + THUMBS_UP + "y");
207+
_testAposFieldNameByteBuffer(bytesPerRead,
208+
ESC_THUMBS + ESC_GCLEF, THUMBS_UP + G_CLEF);
209+
210+
// Long prefix
211+
_testAposFieldNameByteBuffer(bytesPerRead,
212+
"abcdefghijklm" + ESC_THUMBS,
213+
"abcdefghijklm" + THUMBS_UP);
214+
215+
// Long with mixed surrogates
216+
_testAposFieldNameByteBuffer(bytesPerRead,
217+
"abcdefgh" + ESC_THUMBS + "ij" + ESC_GCLEF + "klmn",
218+
"abcdefgh" + THUMBS_UP + "ij" + G_CLEF + "klmn");
219+
}
220+
221+
/*
222+
/**********************************************************************
223+
/* Test methods, error cases
224+
/**********************************************************************
225+
*/
226+
227+
@Test
228+
void loneHighSurrogateInFieldNameByteBuffer() throws Exception
229+
{
230+
String doc = "{\"\\ud83d\":\"value\"}";
231+
byte[] data = _jsonDoc(doc);
232+
try (AsyncReaderWrapper r = asyncForByteBuffer(FACTORY, 1, data, 0)) {
233+
assertToken(JsonToken.START_OBJECT, r.nextToken());
234+
r.nextToken();
235+
fail("Should have thrown for lone high surrogate in field name");
236+
} catch (StreamReadException e) {
237+
verifyException(e, "surrogate");
238+
}
239+
}
240+
241+
@Test
242+
void loneLowSurrogateInFieldNameByteBuffer() throws Exception
243+
{
244+
String doc = "{\"\\udc4d\":\"value\"}";
245+
byte[] data = _jsonDoc(doc);
246+
try (AsyncReaderWrapper r = asyncForByteBuffer(FACTORY, 1, data, 0)) {
247+
assertToken(JsonToken.START_OBJECT, r.nextToken());
248+
r.nextToken();
249+
fail("Should have thrown for lone low surrogate in field name");
250+
} catch (StreamReadException e) {
251+
verifyException(e, "surrogate");
252+
}
253+
}
254+
255+
/*
256+
/**********************************************************************
257+
/* Helper methods
258+
/**********************************************************************
259+
*/
260+
261+
private void _testFieldNameByteBuffer(JsonFactory f, int bytesPerRead,
262+
String escapedName, String expectedName) throws Exception
263+
{
264+
String doc = "{\"" + escapedName + "\":\"value\"}";
265+
byte[] data = _jsonDoc(doc);
266+
try (AsyncReaderWrapper r = asyncForByteBuffer(f, bytesPerRead, data, 0)) {
267+
assertToken(JsonToken.START_OBJECT, r.nextToken());
268+
assertToken(JsonToken.PROPERTY_NAME, r.nextToken());
269+
assertEquals(expectedName, r.currentName());
270+
assertToken(JsonToken.VALUE_STRING, r.nextToken());
271+
assertEquals("value", r.currentText());
272+
assertToken(JsonToken.END_OBJECT, r.nextToken());
273+
}
274+
}
275+
276+
private void _testAposFieldNameByteBuffer(int bytesPerRead,
277+
String escapedName, String expectedName) throws Exception
278+
{
279+
String doc = "{'" + escapedName + "':'value'}";
280+
byte[] data = _jsonDoc(doc);
281+
try (AsyncReaderWrapper r = asyncForByteBuffer(APOS_FACTORY, bytesPerRead, data, 0)) {
282+
assertToken(JsonToken.START_OBJECT, r.nextToken());
283+
assertToken(JsonToken.PROPERTY_NAME, r.nextToken());
284+
assertEquals(expectedName, r.currentName());
285+
assertToken(JsonToken.VALUE_STRING, r.nextToken());
286+
assertEquals("value", r.currentText());
287+
assertToken(JsonToken.END_OBJECT, r.nextToken());
288+
}
289+
}
290+
}

0 commit comments

Comments
 (0)