Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions java/src/org/openqa/selenium/json/Input.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,13 @@
* read characters in the input buffer.
*/
class Input {
/** end-of-file indicator (0xFFFD) */
public static final char EOF = (char) -1; // NOTE: Produces Unicode replacement character (0xFFFD)
/**
* End-of-input sentinel returned by {@link #peek()} and {@link #read()}.
*
* <p>Value {@code -1} mirrors {@link java.io.Reader#read()} and — unlike a {@code char} sentinel
* — cannot collide with any valid UTF-16 code unit (including U+FFFF).
*/
public static final int EOF = -1;

/** the number of chars to buffer */
private static final int BUFFER_SIZE = 4096;
Expand Down Expand Up @@ -64,18 +69,20 @@ public Input(Reader source) {
/**
* Extract the next character from the input without consuming it.
*
* @return the next input character; {@link #EOF} if input is exhausted
* @return the next input character as an unsigned UTF-16 code unit (0-65535); {@link #EOF} if
* input is exhausted
*/
public char peek() {
public int peek() {
return fill() ? buffer[position + 1] : EOF;
}

/**
* Read and consume the next character from the input.
*
* @return the next input character; {@link #EOF} if input is exhausted
* @return the next input character as an unsigned UTF-16 code unit (0-65535); {@link #EOF} if
* input is exhausted
*/
public char read() {
public int read() {
return fill() ? buffer[++position] : EOF;
}

Expand Down
31 changes: 15 additions & 16 deletions java/src/org/openqa/selenium/json/JsonInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ public JsonType peek() {
return JsonType.END;

default:
char c = input.read();
throw new JsonException("Unable to determine type from: " + c + ". " + input);
int c = input.read();
throw new JsonException("Unable to determine type from: " + (char) c + ". " + input);
}
}

Expand Down Expand Up @@ -194,10 +194,10 @@ public String nextName() {

String name = readString();
skipWhitespace(input);
char read = input.read();
int read = input.read();
if (read != ':') {
throw new JsonException(
"Unable to read name. Expected colon separator, but saw '" + read + "'");
"Unable to read name. Expected colon separator, but saw '" + (char) read + "'");
}
return name;
}
Expand Down Expand Up @@ -241,13 +241,13 @@ public Number nextNumber() {
case '7':
case '8':
case '9':
builder.append(input.read());
builder.append((char) input.read());
break;
case '.':
case 'e':
case 'E':
mightBeDecimal = true;
builder.append(input.read());
builder.append((char) input.read());
break;
default:
read = false;
Expand Down Expand Up @@ -552,11 +552,11 @@ private void expect(JsonType type) {

int toCompareLength = toCompare.length();
for (int i = 0; i < toCompareLength; i++) {
char read = input.read();
int read = input.read();
if (read != toCompare.charAt(i)) {
throw new JsonException(
String.format(
"Unable to read %s. Saw %s at position %d. %s", toCompare, read, i, input));
"Unable to read %s. Saw %s at position %d. %s", toCompare, (char) read, i, input));
}
}

Expand All @@ -574,9 +574,8 @@ private String readString() {
input.read(); // Skip leading quote

StringBuilder builder = new StringBuilder();
char c;
while (true) {
c = input.read();
int c = input.read();
switch (c) {
case Input.EOF:
throw new JsonException("Unterminated string: " + builder + ". " + input);
Expand All @@ -586,7 +585,7 @@ private String readString() {
readEscape(builder);
break;
default:
builder.append(c);
builder.append((char) c);
}
}
}
Expand All @@ -601,7 +600,7 @@ private String readString() {
*/
// FIXME: This function doesn't appear to support UTF-8 or UTF-32.
private void readEscape(StringBuilder builder) {
char read = input.read();
int read = input.read();

// List from: https://tools.ietf.org/html/rfc7159.html#section-7
switch (read) {
Expand Down Expand Up @@ -629,10 +628,10 @@ private void readEscape(StringBuilder builder) {
int result = 0;
int multiplier = 4096; // (16 * 16 * 16) as we start from the thousands and work to units.
for (int i = 0; i < 4; i++) {
char c = input.read();
int c = input.read();
int digit = Character.digit(c, 16);
if (digit == -1) {
throw new JsonException(c + " is not a hexadecimal digit. " + input);
throw new JsonException((char) c + " is not a hexadecimal digit. " + input);
}
result += digit * multiplier;
multiplier /= 16;
Expand All @@ -643,11 +642,11 @@ private void readEscape(StringBuilder builder) {
case '/':
case '\\':
case '"':
builder.append(read);
builder.append((char) read);
break;

default:
throw new JsonException("Unexpected escape code: " + read + ". " + input);
throw new JsonException("Unexpected escape code: " + (char) read + ". " + input);
}
}

Expand Down
18 changes: 18 additions & 0 deletions java/test/org/openqa/selenium/json/JsonInputTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,24 @@ void shouldBeAbleToReadNonWellFormedDataLongerThanReadBuffer() {
}
}

@Test
void shouldReadU_FFFF_AsALiteralCharacterAndNotEndOfInput() {
// U+FFFF is a valid Unicode code unit that historically collided with the in-band EOF
// sentinel and was mis-reported as an unterminated string. Build the strings from
// char values rather than embedding literal U+FFFF so the test is independent of the
// source file's byte encoding.
char nonChar = (char) 0xFFFF;
String literalPayload = "a" + nonChar + "b";

try (JsonInput input = newInput("\"" + literalPayload + "\"")) {
assertThat(input.nextString()).isEqualTo(literalPayload);
}

try (JsonInput input = newInput("\"\\uFFFF\"")) {
assertThat(input.nextString()).isEqualTo(String.valueOf(nonChar));
}
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
}

@Test
void nullInputsShouldCoerceAsNullValues() throws IOException {
try (InputStream is = new ByteArrayInputStream(new byte[0]);
Expand Down
Loading