check lone surrogate (compatible with utf8.js v2.1.0)

This commit is contained in:
nkzawa
2015-10-11 01:10:06 +09:00
parent 0974154181
commit 45977f1a90
8 changed files with 88 additions and 47 deletions

View File

@@ -5,6 +5,7 @@ import io.socket.emitter.Emitter;
import io.socket.engineio.parser.Packet;
import io.socket.engineio.parser.Parser;
import io.socket.thread.EventThread;
import io.socket.utf8.UTF8Exception;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
@@ -98,7 +99,11 @@ public abstract class Transport extends Emitter {
@Override
public void run() {
if (Transport.this.readyState == ReadyState.OPEN) {
try {
Transport.this.write(packets);
} catch (UTF8Exception err) {
throw new RuntimeException(err);
}
} else {
throw new RuntimeException("Transport not open");
}
@@ -129,7 +134,7 @@ public abstract class Transport extends Emitter {
this.emit(EVENT_CLOSE);
}
abstract protected void write(Packet[] packets);
abstract protected void write(Packet[] packets) throws UTF8Exception;
abstract protected void doOpen();

View File

@@ -7,6 +7,7 @@ import io.socket.engineio.parser.Parser;
import io.socket.parseqs.ParseQS;
import io.socket.thread.EventThread;
import io.socket.emitter.Emitter;
import io.socket.utf8.UTF8Exception;
import java.util.Date;
import java.util.HashMap;
@@ -152,7 +153,11 @@ abstract public class Polling extends Transport {
@Override
public void call(Object... args) {
logger.fine("writing close packet");
try {
self.write(new Packet[]{new Packet(Packet.CLOSE)});
} catch (UTF8Exception err) {
throw new RuntimeException(err);
}
}
};
@@ -167,7 +172,7 @@ abstract public class Polling extends Transport {
}
}
protected void write(Packet[] packets) {
protected void write(Packet[] packets) throws UTF8Exception {
final Polling self = this;
this.writable = false;
final Runnable callbackfn = new Runnable() {

View File

@@ -12,6 +12,7 @@ import com.squareup.okhttp.Response;
import com.squareup.okhttp.ws.WebSocket.PayloadType;
import com.squareup.okhttp.ws.WebSocketCall;
import com.squareup.okhttp.ws.WebSocketListener;
import io.socket.utf8.UTF8Exception;
import okio.Buffer;
import okio.BufferedSource;
@@ -142,7 +143,7 @@ public class WebSocket extends Transport {
client.getDispatcher().getExecutorService().shutdown();
}
protected void write(Packet[] packets) {
protected void write(Packet[] packets) throws UTF8Exception {
final WebSocket self = this;
this.writable = false;
for (Packet packet : packets) {

View File

@@ -38,11 +38,11 @@ public class Parser {
private Parser() {}
public static void encodePacket(Packet packet, EncodeCallback callback) {
public static void encodePacket(Packet packet, EncodeCallback callback) throws UTF8Exception {
encodePacket(packet, false, callback);
}
public static void encodePacket(Packet packet, boolean utf8encode, EncodeCallback callback) {
public static void encodePacket(Packet packet, boolean utf8encode, EncodeCallback callback) throws UTF8Exception {
if (packet.data instanceof byte[]) {
@SuppressWarnings("unchecked")
Packet<byte[]> _packet = packet;
@@ -109,7 +109,7 @@ public class Parser {
return new Packet<byte[]>(packetslist.get(type), intArray);
}
public static void encodePayload(Packet[] packets, EncodeCallback<byte[]> callback) {
public static void encodePayload(Packet[] packets, EncodeCallback<byte[]> callback) throws UTF8Exception {
if (packets.length == 0) {
callback.call(new byte[0]);
return;

View File

@@ -14,8 +14,8 @@ public class UTF8 {
private static int byteCount;
private static int byteIndex;
public static String encode(String string) {
int[] codePoints = uc2decode(string);
public static String encode(String string) throws UTF8Exception {
int[] codePoints = ucs2decode(string);
int length = codePoints.length;
int index = -1;
int codePoint;
@@ -28,7 +28,7 @@ public class UTF8 {
}
public static String decode(String byteString) throws UTF8Exception {
byteArray = uc2decode(byteString);
byteArray = ucs2decode(byteString);
byteCount = byteArray.length;
byteIndex = 0;
List<Integer> codePoints = new ArrayList<Integer>();
@@ -39,7 +39,7 @@ public class UTF8 {
return ucs2encode(listToArray(codePoints));
}
private static int[] uc2decode(String string) {
private static int[] ucs2decode(String string) {
int length = string.length();
int[] output = new int[string.codePointCount(0, length)];
int counter = 0;
@@ -51,7 +51,7 @@ public class UTF8 {
return output;
}
private static String encodeCodePoint(int codePoint) {
private static String encodeCodePoint(int codePoint) throws UTF8Exception {
StringBuilder symbol = new StringBuilder();
if ((codePoint & 0xFFFFFF80) == 0) {
return symbol.append(Character.toChars(codePoint)).toString();
@@ -59,6 +59,7 @@ public class UTF8 {
if ((codePoint & 0xFFFFF800) == 0) {
symbol.append(Character.toChars(((codePoint >> 6) & 0x1F) | 0xC0));
} else if ((codePoint & 0xFFFF0000) == 0) {
checkScalarValue(codePoint);
symbol.append(Character.toChars(((codePoint >> 12) & 0x0F) | 0xE0));
symbol.append(createByte(codePoint, 6));
} else if ((codePoint & 0xFFE00000) == 0) {
@@ -111,6 +112,7 @@ public class UTF8 {
byte3 = readContinuationByte();
codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3;
if (codePoint >= 0x0800) {
checkScalarValue(codePoint);
return codePoint;
} else {
throw new UTF8Exception("Invalid continuation byte");
@@ -153,6 +155,15 @@ public class UTF8 {
return output.toString();
}
private static void checkScalarValue(int codePoint) throws UTF8Exception {
if (codePoint >= 0xD800 && codePoint <= 0xDFFF) {
throw new UTF8Exception(
"Lone surrogate U+" + Integer.toHexString(codePoint).toUpperCase() +
" is not a scalar value"
);
}
}
private static int[] listToArray(List<Integer> list) {
int size = list.size();
int[] array = new int[size];

View File

@@ -72,7 +72,7 @@ public class ConnectionTest extends Connection {
socket.on(Socket.EVENT_OPEN, new Emitter.Listener() {
@Override
public void call(Object... args) {
socket.send("\uD800-\uDB7F\uDB80-\uDBFF\uDC00-\uDFFF\uE000-\uF8FF");
socket.send("\uD800\uDC00-\uDB7F\uDFFF\uDB80\uDC00-\uDBFF\uDFFF\uE000-\uF8FF");
socket.on(Socket.EVENT_MESSAGE, new Emitter.Listener() {
@Override
public void call(Object... args) {
@@ -85,7 +85,7 @@ public class ConnectionTest extends Connection {
});
socket.open();
assertThat((String)values.take(), is("\uD800-\uDB7F\uDB80-\uDBFF\uDC00-\uDFFF\uE000-\uF8FF"));
assertThat((String)values.take(), is("\uD800\uDC00-\uDB7F\uDFFF\uDB80\uDC00-\uDBFF\uDFFF\uE000-\uF8FF"));
}
@Test(timeout = TIMEOUT)

View File

@@ -1,5 +1,6 @@
package io.socket.engineio.parser;
import io.socket.utf8.UTF8Exception;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -14,7 +15,7 @@ public class ParserTest {
static final String ERROR_DATA = "parser error";
@Test
public void encodeAsString() {
public void encodeAsString() throws UTF8Exception {
encodePacket(new Packet<String>(Packet.MESSAGE, "test"), new EncodeCallback<String>() {
@Override
public void call(String data) {
@@ -24,7 +25,7 @@ public class ParserTest {
}
@Test
public void decodeAsPacket() {
public void decodeAsPacket() throws UTF8Exception {
encodePacket(new Packet<String>(Packet.MESSAGE, "test"), new EncodeCallback<String>() {
@Override
public void call(String data) {
@@ -34,7 +35,7 @@ public class ParserTest {
}
@Test
public void noData() {
public void noData() throws UTF8Exception {
encodePacket(new Packet(Packet.MESSAGE), new EncodeCallback<String>() {
@Override
public void call(String data) {
@@ -46,7 +47,7 @@ public class ParserTest {
}
@Test
public void encodeOpenPacket() {
public void encodeOpenPacket() throws UTF8Exception {
encodePacket(new Packet<String>(Packet.OPEN, "{\"some\":\"json\"}"), new EncodeCallback<String>() {
@Override
public void call(String data) {
@@ -58,7 +59,7 @@ public class ParserTest {
}
@Test
public void encodeClosePacket() {
public void encodeClosePacket() throws UTF8Exception {
encodePacket(new Packet<String>(Packet.CLOSE), new EncodeCallback<String>() {
@Override
public void call(String data) {
@@ -69,7 +70,7 @@ public class ParserTest {
}
@Test
public void encodePingPacket() {
public void encodePingPacket() throws UTF8Exception {
encodePacket(new Packet<String>(Packet.PING, "1"), new EncodeCallback<String>() {
@Override
public void call(String data) {
@@ -81,7 +82,7 @@ public class ParserTest {
}
@Test
public void encodePongPacket() {
public void encodePongPacket() throws UTF8Exception {
encodePacket(new Packet<String>(Packet.PONG, "1"), new EncodeCallback<String>() {
@Override
public void call(String data) {
@@ -93,7 +94,7 @@ public class ParserTest {
}
@Test
public void encodeMessagePacket() {
public void encodeMessagePacket() throws UTF8Exception {
encodePacket(new Packet<String>(Packet.MESSAGE, "aaa"), new EncodeCallback<String>() {
@Override
public void call(String data) {
@@ -105,7 +106,7 @@ public class ParserTest {
}
@Test
public void encodeUTF8SpecialCharsMessagePacket() {
public void encodeUTF8SpecialCharsMessagePacket() throws UTF8Exception {
encodePacket(new Packet<String>(Packet.MESSAGE, "utf8 — string"), new EncodeCallback<String>() {
@Override
public void call(String data) {
@@ -117,7 +118,7 @@ public class ParserTest {
}
@Test
public void encodeMessagePacketCoercingToString() {
public void encodeMessagePacketCoercingToString() throws UTF8Exception {
encodePacket(new Packet<Integer>(Packet.MESSAGE, 1), new EncodeCallback<String>() {
@Override
public void call(String data) {
@@ -129,7 +130,7 @@ public class ParserTest {
}
@Test
public void encodeUpgradePacket() {
public void encodeUpgradePacket() throws UTF8Exception {
encodePacket(new Packet<String>(Packet.UPGRADE), new EncodeCallback<String>() {
@Override
public void call(String data) {
@@ -140,7 +141,7 @@ public class ParserTest {
}
@Test
public void encodingFormat() {
public void encodingFormat() throws UTF8Exception {
encodePacket(new Packet<String>(Packet.MESSAGE, "test"), new EncodeCallback<String>() {
@Override
public void call(String data) {
@@ -177,7 +178,7 @@ public class ParserTest {
}
@Test
public void encodePayloads() {
public void encodePayloads() throws UTF8Exception {
encodePayload(new Packet[]{new Packet(Packet.PING), new Packet(Packet.PONG)}, new EncodeCallback<byte[]>() {
@Override
public void call(byte[] data) {
@@ -187,7 +188,7 @@ public class ParserTest {
}
@Test
public void encodeAndDecodePayloads() {
public void encodeAndDecodePayloads() throws UTF8Exception {
encodePayload(new Packet[] {new Packet<String>(Packet.MESSAGE, "a")}, new EncodeCallback<byte[]>() {
@Override
public void call(byte[] data) {
@@ -221,7 +222,7 @@ public class ParserTest {
}
@Test
public void encodeAndDecodeEmptyPayloads() {
public void encodeAndDecodeEmptyPayloads() throws UTF8Exception {
encodePayload(new Packet[] {}, new EncodeCallback<byte[]>() {
@Override
public void call(byte[] data) {
@@ -335,7 +336,7 @@ public class ParserTest {
}
@Test
public void encodeBinaryMessage() {
public void encodeBinaryMessage() throws UTF8Exception {
final byte[] data = new byte[5];
for (int i = 0; i < data.length; i++) {
data[0] = (byte)i;
@@ -351,7 +352,7 @@ public class ParserTest {
}
@Test
public void encodeBinaryContents() {
public void encodeBinaryContents() throws UTF8Exception {
final byte[] firstBuffer = new byte[5];
for (int i = 0 ; i < firstBuffer.length; i++) {
firstBuffer[0] = (byte)i;
@@ -385,7 +386,7 @@ public class ParserTest {
}
@Test
public void encodeMixedBinaryAndStringContents() {
public void encodeMixedBinaryAndStringContents() throws UTF8Exception {
final byte[] firstBuffer = new byte[123];
for (int i = 0 ; i < firstBuffer.length; i++) {
firstBuffer[0] = (byte)i;

View File

@@ -26,25 +26,25 @@ public class UTF8Test {
new Data(0x07FF, "\uFFFF", "\u00EF\u00BF\u00BF"),
// unmatched surrogate halves
// high surrogates: 0xD800 to 0xDBFF
new Data(0xD800, "\uD800", "\u00ED\u00A0\u0080"),
new Data(0xD800, "\uD800", "\u00ED\u00A0\u0080", true),
new Data("High surrogate followed by another high surrogate",
"\uD800\uD800", "\u00ED\u00A0\u0080\u00ED\u00A0\u0080"),
"\uD800\uD800", "\u00ED\u00A0\u0080\u00ED\u00A0\u0080", true),
new Data("High surrogate followed by a symbol that is not a surrogate",
"\uD800A", "\u00ED\u00A0\u0080A"),
"\uD800A", "\u00ED\u00A0\u0080A", true),
new Data("Unmatched high surrogate, followed by a surrogate pair, followed by an unmatched high surrogate",
"\uD800\uD834\uDF06\uD800", "\u00ED\u00A0\u0080\u00F0\u009D\u008C\u0086\u00ED\u00A0\u0080"),
new Data(0xD9AF, "\uD9AF", "\u00ED\u00A6\u00AF"),
new Data(0xDBFF, "\uDBFF", "\u00ED\u00AF\u00BF"),
"\uD800\uD834\uDF06\uD800", "\u00ED\u00A0\u0080\u00F0\u009D\u008C\u0086\u00ED\u00A0\u0080", true),
new Data(0xD9AF, "\uD9AF", "\u00ED\u00A6\u00AF", true),
new Data(0xDBFF, "\uDBFF", "\u00ED\u00AF\u00BF", true),
// low surrogates: 0xDC00 to 0xDFFF
new Data(0xDC00, "\uDC00", "\u00ED\u00B0\u0080"),
new Data(0xDC00, "\uDC00", "\u00ED\u00B0\u0080", true),
new Data("Low surrogate followed by another low surrogate",
"\uDC00\uDC00", "\u00ED\u00B0\u0080\u00ED\u00B0\u0080"),
"\uDC00\uDC00", "\u00ED\u00B0\u0080\u00ED\u00B0\u0080", true),
new Data("Low surrogate followed by a symbol that is not a surrogate",
"\uDC00A", "\u00ED\u00B0\u0080A"),
"\uDC00A", "\u00ED\u00B0\u0080A", true),
new Data("Unmatched low surrogate, followed by a surrogate pair, followed by an unmatched low surrogate",
"\uDC00\uD834\uDF06\uDC00", "\u00ED\u00B0\u0080\u00F0\u009D\u008C\u0086\u00ED\u00B0\u0080"),
new Data(0xDEEE, "\uDEEE", "\u00ED\u00BB\u00AE"),
new Data(0xDFFF, "\uDFFF", "\u00ED\u00BF\u00BF"),
"\uDC00\uD834\uDF06\uDC00", "\u00ED\u00B0\u0080\u00F0\u009D\u008C\u0086\u00ED\u00B0\u0080", true),
new Data(0xDEEE, "\uDEEE", "\u00ED\u00BB\u00AE", true),
new Data(0xDFFF, "\uDFFF", "\u00ED\u00BF\u00BF", true),
// 4-byte
new Data(0x010000, "\uD800\uDC00", "\u00F0\u0090\u0080\u0080"),
new Data(0x01D306, "\uD834\uDF06", "\u00F0\u009D\u008C\u0086"),
@@ -58,9 +58,16 @@ public class UTF8Test {
public void encodeAndDecode() throws UTF8Exception {
for (Data data : DATA) {
String reason = data.description != null? data.description : "U+" + Integer.toHexString(data.codePoint).toUpperCase();
if (data.error) {
exception.expect(UTF8Exception.class);
UTF8.decode(data.encoded);
exception.expect(UTF8Exception.class);
UTF8.encode(data.decoded);
} else {
assertThat("Encoding: " + reason, data.encoded, is(UTF8.encode(data.decoded)));
assertThat("Decoding: " + reason, data.decoded, is(UTF8.decode(data.encoded)));
}
}
exception.expect(UTF8Exception.class);
UTF8.decode("\uFFFF");
@@ -80,17 +87,28 @@ public class UTF8Test {
public String description;
public String decoded;
public String encoded;
public boolean error;
public Data(int codePoint, String decoded, String encoded) {
this(codePoint, decoded, encoded, false);
}
public Data(int codePoint, String decoded, String encoded, boolean error) {
this.codePoint = codePoint;
this.decoded = decoded;
this.encoded = encoded;
this.error = error;
}
public Data(String description, String decoded, String encoded) {
this(description, decoded, encoded, false);
}
public Data(String description, String decoded, String encoded, boolean error) {
this.description = description;
this.decoded = decoded;
this.encoded = encoded;
this.error = error;
}
}
}